123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351(* This file is free software, part of Zipperposition. See file "license" for more details. *)(** {1 AC redundancy} *)openLogtkmoduleT=TermmoduleLit=LiteralopenAC_intfletsection=Util.Section.make"AC"letprof_simplify=ZProf.make"AC.simplify"letstat_ac_simplify=Util.mk_stat"AC.simplify"letstat_ac_redundant=Util.mk_stat"AC.redundant"(* flag for clauses that are not AC redundant because they are axioms *)letflag_axiom=SClause.new_flag()typespec=AC_intf.specmoduletypeS=AC_intf.Sletkey_ac:(moduleS)Flex_state.key=Flex_state.create_key()letkey_scan_cl_ac=Flex_state.create_key()let_scan_cl_ac=reffalsemoduleMake(Env:Env.S):SwithmoduleEnv=Env=structmoduleCtx=Env.CtxmoduleEnv=EnvmoduleC=Env.Ctypecell={spec:spec;proof:Proof.parent;axioms:C.tlist;(* ground-complete set of axioms (see "E: a brainiac theorem prover") *)}lettbl:cellID.Tbl.t=ID.Tbl.create3leton_add:specSignal.t=Signal.create()letmk_axioms_proofsty:C.tlist=letty_args_n,ty_args,_ty_ret=Type.open_poly_funtyinifList.lengthty_args<>2then(Util.errorf~where:"AC""AC symbol `%a`must be of arity 2"ID.pps;);(* create type variables, for polymorphic AC symbols *)letty_vars=CCList.initty_args_n(funi->HVar.make~ty:Type.tTypei)inletty_vars_t=List.mapType.varty_varsin(* type applied to the new variables *)letty'=Type.applytyty_vars_tinletn',ty_args,ty_ret=Type.open_poly_funty'inassert(n'=0);(* check consistency of types *)beginmatchty_argswith|[a;b]->ifnot(Type.equalab&&Type.equalaty_ret)then(Util.errorf~where:"AC""AC symbol `%a` argument types must be `@[%a@]`"ID.ppsType.ppty_ret;);|_->assertfalseend;letx=T.var_of_int~ty:ty_ret(ty_args_n+1)inlety=T.var_of_int~ty:ty_ret(ty_args_n+2)inletz=T.var_of_int~ty:ty_ret(ty_args_n+3)inletfxy=T.app_full(T.const~tys)ty_vars_t[x;y]in(* build clause l=r *)letmk_clauselr=letpenalty=1inletproof=Proof.Step.esa~rule:(Proof.Rule.mk"ac")[proof]inletc=C.create~trail:Trail.empty~penalty[Lit.mk_eqlr]proofinC.set_flagflag_axiomctrue;C.set_flagSClause.flag_persistentctrue;cin[mk_clause(fxy)(fyx);mk_clause(f(fxy)z)(fx(fyz));mk_clause(fx(fyz))(fz(fxy));mk_clause(fx(fyz))(fy(fxz));mk_clause(fx(fyz))(fz(fyx))]letadd_proof~tys=tryID.Tbl.findtblswithNot_found->letspec={ty;sym=s}inletaxioms=mk_axioms_proofstyinletcell={spec;proof;axioms;}inID.Tbl.addtblscell;Signal.sendon_addspec;cellletis_acs=ID.Tbl.memtblsletexists_ac()=ID.Tbl.lengthtbl>0letfind_proofs=(ID.Tbl.findtbls).proofletsymbols()=ID.Tbl.keystbl|>ID.Set.of_itermoduleA=T.AC(structletis_ac=is_acletis_comm_=falseend)letsymbols_of_termsseq=A.symbolsseq(** {2 Rules} *)(* does [l ?= r] have at least one AC symbol in it? *)lethas_ac_ids_lr=letseq=Iter.doubletonlr|>Iter.flat_mapA.seq_symbolsinnot(Iter.is_emptyseq)letis_trivial_litlit=exists_ac()&&(matchlitwith|Lit.Equation(l,r,true)->has_ac_ids_lr&&A.equallr|_->false)letis_trivialc=letres=not(C.get_flagflag_axiomc)&&CCArray.existsis_trivial_lit(C.litsc)inifresthen(Util.incr_statstat_ac_redundant;Util.debugf~section3"@[<2>clause `@[%a@]`@ is AC-trivial@]"(funk->kC.ppc););res(* simplify: remove literals that are redundant modulo AC *)letsimplifyc=ZProf.enter_profprof_simplify;ifexists_ac()then(letn=Array.length(C.litsc)inletlits=Array.to_list(C.litsc)inletlits=List.filter(funlit->matchlitwith|Literal.Equation(l,r,false)->not(has_ac_ids_lr&&A.equallr)|_->true)litsinletn'=List.lengthlitsinifn'<n&¬(C.get_flagSClause.flag_persistentc)then((* did some simplification *)letsymbols=symbols_of_terms(C.Seq.termsc)inletsymbols=ID.Set.to_listsymbolsinlettags=List.map(funid->Builtin.Tag.T_acid)symbolsinletpremises=C.proof_parentc::List.map(funid->(ID.Tbl.findtblid).proof)symbolsinletproof=Proof.Step.simppremises~rule:(Proof.Rule.mk"AC.normalize")~tagsinletnew_c=C.create~trail:(C.trailc)~penalty:(C.penaltyc)litsproofinZProf.exit_profprof_simplify;Util.incr_statstat_ac_simplify;Util.debugf~section3"@[<2>@[%a@]@ AC-simplify into @[%a@]@]"(funk->kC.ppcC.ppnew_c);SimplM.return_newnew_c)else((* no simplification *)ZProf.exit_profprof_simplify;SimplM.return_samec))elseSimplM.return_samecletinstall_rules_()=Env.add_is_trivialis_trivial;Env.add_basic_simplifysimplify;()letadd~proofsty=Util.debugf~section1"@[enable AC redundancy criterion@ for `@[%a : @[%a@]@]`@ :proof %a@]"(funk->kID.ppsType.pptyProof.pp_parentproof);(* is this the first case of AC symbols? If yes, then add inference rules *)letfirst=not(exists_ac())iniffirsttheninstall_rules_();(* remember that the symbols is AC *)letcell=add_proof~tysin(* add clauses *)Util.debugf~section3"@[<2>add AC axioms for `%a : @[%a@]`:@ @[<hv>%a@]@]"(funk->kID.ppsType.ppty(Util.pp_listC.pp)cell.axioms);(* add axioms to either passive, or active set *)ifEnv.ProofState.ActiveSet.clauses()|>C.ClauseSet.for_all(C.get_flagflag_axiom)then((* the only active clauses are other AC axioms, we miss no
inference by adding the axioms to active set directly *)Env.add_active(Iter.of_listcell.axioms))else(Env.add_passive(Iter.of_listcell.axioms););()(* TODO: proof stuff *)letscan_statementst=letmoduleSt=Statementinlethas_ac_attr=List.exists(functionSt.A_AC->true|_->false)(Statement.attrsst)inifhas_ac_attrthen(letproof=Proof.Parent.from@@St.as_proof_cstinbeginmatchSt.viewstwith|St.TyDecl(id,ty)->add~proofidty|St.Defl->List.iter(fun{Statement.def_id;def_ty;_}->add~proofdef_iddef_ty)l|St.Data_|St.Rewrite_|St.Assert_|St.Lemma_|St.Goal_|St.NegatedGoal_->Util.error~where:"AC""attribute 'AC' only supported on def/decl statements"end)letregister_accidty=add(C.proof_parentc)idtyletscan_clausec=letexceptionFailinUtil.debugf~section1"Scanning @[%a@]@."(funk->kC.ppc);letfail_oncond=ifcondthenraiseFailinletis_binary_symhd_s=List.length(Type.expected_args(T.tyhd_s))==2in(* commutativity test is symmetric *)lettest_commutativtyst=tryUtil.debugf~section1"Testing commutativity @[(%a,%a)@]@."(funk->kT.ppsT.ppt);beginmatchT.views,T.viewtwith|T.App(hd_s,[x_s;y_s]),T.App(hd_t,[x_t;y_t])->fail_on(not(T.equalhd_shd_t));fail_on(not(T.is_consthd_s));fail_on(not(is_binary_symhd_s));fail_on(not(T.is_varx_s)||not(T.is_vary_s));fail_on(not(T.is_varx_t)||not(T.is_vary_t));fail_on(T.equalx_sy_s);fail_on(T.equalx_ty_t);fail_on(not(T.equalx_sy_t&&T.equaly_sx_t));Util.debugf~section1"Commutativity recognized@."(funk->k);true|_->falseendwithFail->falsein(* associativity test is NOT symmetric:
it specifically checks for equation of the kind
f X (f Y Z) = f (f X Y) Z *)lettest_associativityst=tryUtil.debugf~section1"Testing associativity @[(%a,%a)@]@."(funk->kT.ppsT.ppt);beginmatchT.views,T.viewtwith|T.App(hd_s,[x_s;fyz_s]),T.App(hd_t,[fxy_t;z_t])->beginmatchT.viewfyz_s,T.viewfxy_twith|T.App(hd_s',[y_s;z_s]),T.App(hd_t',[x_t;y_t])->fail_on(not(T.equalhd_shd_t&&T.equalhd_thd_s'&&T.equalhd_s'hd_t'));fail_on(not(T.is_consthd_s));fail_on(not(is_binary_symhd_s));fail_on(not(List.for_allT.is_var[x_s;y_s;z_s]));fail_on(not(List.for_allT.is_var[x_t;y_t;z_t]));fail_on(T.Set.cardinal(T.Set.of_list[x_s;y_s;z_s])!=3);fail_on(not(T.equalx_sx_t&&T.equaly_sy_t&&T.equalz_sz_t));Util.debugf~section1"Associativity recognized @."(funk->k);true|_->falseend|_->falseendwithFail->falseinmatchC.litscwith|[|Literal.Equation(lhs,rhs,true)|]->letty=T.ty(T.head_termlhs)inCCOpt.iter(funid->ifnot(ID.is_acid)then(ifID.is_commidthen(iftest_associativitylhsrhs||test_associativityrhslhsthen(ID.set_payloadidID.Attr_assoc;assert(ID.is_acid);register_accidty))elseifID.is_associdthen(iftest_commutativtylhsrhsthen(ID.set_payloadidID.Attr_comm;assert(ID.is_acid);register_accidty))else(iftest_commutativtylhsrhsthen(ID.set_payloadidID.Attr_comm;assert(ID.is_commid);)elseiftest_associativitylhsrhs||test_associativityrhslhsthen(ID.set_payloadidID.Attr_assoc;assert(ID.is_associd);))))(T.headlhs)|_->()(* just look for AC axioms *)letsetup()=Env.flex_addkey_scan_cl_ac!_scan_cl_ac;if!_scan_cl_acthen(Signal.on_everyEnv.ProofState.PassiveSet.on_add_clause(func->scan_clausec;Signal.ContinueListening));Signal.on_everyEnv.on_input_statementscan_statementendletextension=letactionenv=letmoduleE=(valenv:Env.S)inletmoduleAC=Make(E)inE.flex_addkey_ac(moduleAC:S);AC.setup()in{Extensions.defaultwithExtensions.name="ac";env_actions=[action];}let()=Options.add_opts["--scan-clause-ac",Arg.Bool((:=)_scan_cl_ac)," scan clauses for AC definitions"]