12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400(* This file is free software, part of Zipperposition. See file "license" for more details. *)(** {1 Induction through Cut} *)openLogtkopenLibzipperpositionmoduleLits=LiteralsmoduleT=TermmoduleTy=TypemoduleFmt=CCFormatmoduleRW=RewritemoduleAvatar=Libzipperposition_avatarmoduletypeAVATAR=Libzipperposition_avatar.SmoduletypeS=Induction_intf.Stypeterm=T.ttypevar=T.vartypeclause=Literals.ttypeform=clauselistletsection=Util.Section.make~parent:Const.section"induction"letstat_lemmas=Util.mk_stat"induction.inductive_lemmas"letstat_trivial_lemmas=Util.mk_stat"induction.trivial_lemmas"letstat_absurd_lemmas=Util.mk_stat"induction.absurd_lemmas"letstat_goal_duplicate=Util.mk_stat"induction.duplicate_goal"letstat_inductions=Util.mk_stat"induction.inductions"letstat_split_goal=Util.mk_stat"induction.split_goals"letstat_generalize=Util.mk_stat"induction.generalize"letstat_generalize_vars_active_pos=Util.mk_stat"induction.generalize_vars_active_pos"letstat_generalize_terms_active_pos=Util.mk_stat"induction.generalize_terms_active_pos"letstat_assess_goal=Util.mk_stat"induction.assess_goal_calls"letstat_assess_goal_ok=Util.mk_stat"induction.assess_goal_ok"letprof_check_goal=ZProf.make"induction.check_goal"letk_enable:boolFlex_state.key=Flex_state.create_key()letk_ind_depth:intFlex_state.key=Flex_state.create_key()letk_limit_to_active:boolFlex_state.key=Flex_state.create_key()letk_coverset_depth:intFlex_state.key=Flex_state.create_key()letk_goal_assess_limit:intFlex_state.key=Flex_state.create_key()letk_ind_on_subcst:boolFlex_state.key=Flex_state.create_key()letk_generalize_var:boolFlex_state.key=Flex_state.create_key()letk_generalize_term:boolFlex_state.key=Flex_state.create_key()(** {2 Formula to be Proved Inductively *)moduleMake_goal(E:Env_intf.S):sigtypetvaltrivial:t(** trivial goal *)valof_form:form->tvalof_cut_form:Cut_form.t->tvalform:t->Cut_form.tvalcs:t->Literals.tlistvalvars:t->T.VarSet.tvalind_vars:t->varlist(** the inductive variables *)valsimplify:t->t(** Apply rewrite rules to the goal *)valsplit:t->tlist(** Split the goal into independent goals to be proved separately
(if there is a conjunction of clauses that share no variables) *)valpp:tCCFormat.printertypestatus=|S_trivial|S_ok|S_falsifiableofSubst.tvaltest:t->status(** Testing using {!Test_prop} *)valcheck_not_absurd_or_trivial:t->bool(** More thorough testing *)valis_acceptable_goal:t->boolvaladd_lemma:Cut_form.t->unit(** Signal that this cut formula is an active lemma *)valhas_been_tried:t->bool(** Is the goal already converted into a lemma? *)end=structtypestatus=|S_trivial|S_ok|S_falsifiableofSubst.t(* formula to be proved inductively. The clauses share some variables,
they are not independent *)typet={cut:Cut_form.t;test_res:statuslazy_t;}lettrivial:t={cut=Cut_form.trivial;test_res=Lazy.from_valS_trivial}(* trivial clause? *)lettrivial_c(c:Literals.t):bool=Literals.is_trivialclettest_(cs:Literals.tlist):status=(* test and save *)ifList.for_alltrivial_ccsthenS_trivialelsebeginmatchTest_prop.check_formcswith|Test_prop.R_ok->S_ok|Test_prop.R_failsubst->S_falsifiablesubstendletof_cut_form(f:Cut_form.t):t=lettest_res=lazy(Cut_form.csf|>test_)in{cut=f;test_res}letof_formcs:t=of_cut_form(Cut_form.makecs)letformt=t.cutletcst:form=Cut_form.cst.cutletvarst=Cut_form.varst.cutlettest(t:t):status=Lazy.forcet.test_resletind_varst=Cut_form.ind_varst.cutletppout(f:t):unit=Cut_form.ppoutf.cutletsimplify(g:t):t=formg|>Cut_form.normalize|>of_cut_form(* union-find for sets of clauses *)moduleUF_clauses=Avatar.UnionFind.Make(structtypekey=T.vartypevalue=clauselistletequal=HVar.equalType.equallethash=HVar.hashletzero=[]letmerge=List.rev_appendend)letsplit(g:t):tlist=letuf=varsg|>T.VarSet.to_list|>UF_clauses.createandground_=ref[]inList.iter(func->letvars=Literals.varscinList.iter(funv->UF_clauses.addufv[c])vars;beginmatchvarswith|[]->(* ground clause is all by itself *)ground_:=[c]::!ground_;|v::other_vars->(* merge together these variables *)List.iter(funv'->UF_clauses.unionufvv')other_vars;end)(csg);letall_clusters=(UF_clauses.to_iteruf|>Iter.mapsnd|>Iter.to_rev_list)@!ground_inletnew_goals=List.rev_mapof_formall_clustersinifList.lengthnew_goals>1then(Util.incr_statstat_split_goal;Util.debugf~section3"(@[<2>split_goal@ :goal %a@ :new_goals (@[<hv>%a@])@])"(funk->kppg(Util.pp_listpp)new_goals););new_goalsmoduleC=E.Clettest_goal_is_ok(g:t):bool=beginmatchtestgwith|S_ok->true|S_trivial->Util.incr_statstat_trivial_lemmas;Util.debugf~section2"(@[<2>lemma_trivial@ @[%a@]@@])"(funk->kppg);false|S_falsifiablesubst->Util.debugf~section2"(@[<2>lemma_absurd@ @[%a@]@ :subst %a@])"(funk->kppgSubst.ppsubst);Util.incr_statstat_absurd_lemmas;falseendexceptionYield_falseofC.t(* do only a few steps of inferences for checking if a candidate lemma
is trivial/absurd *)letmax_steps_=E.flex_getk_goal_assess_limit(* TODO: if goal passes tests, can we use the demod/sup steps to infer active
positions? (e.g. looking at which variables were substituted with
cstor terms) *)(* check that [lemma] is not obviously absurd or trivial, by making a few steps of
superposition inferences between [lemma] and the Active Set.
The strategy here is set of support: no inference between clauses of [lemma]
and no inferences among active clauses, just between active clauses and
those derived from [lemma]. Inferences with trails are dropped because
the lemma should be inconditionally true. *)letcheck_not_absurd_or_trivial_(g:t):bool=Util.debugf~section2"@[<2>@{<green>assess goal@}@ :goal %a@ :max-steps %d@]"(funk->kppgmax_steps_);letmoduleCQ=E.ProofState.CQueueinletq=CQ.almost_bfs()in(* clauses waiting *)letpush_cc=ignore(CQ.addqc)inletn:intref=ref0in(* number of steps *)lettrivial=reftrueintry(* add goal's clauses to the local saturation set *)List.iter(funlits->letc=C.create_a~trail:Trail.empty~penalty:1litsProof.Step.trivialinletc,_=E.unary_simplifycinifE.is_trivialcthen()elseifC.is_emptycthenraise(Yield_falsec)else(trivial:=false;ignore(push_cc)))(csg);(* do a few steps of saturation *)whilenot(CQ.is_emptyq)&&!n<max_steps_dotryincrn;letc=CQ.take_firstqinletc,_=E.unary_simplifycin(* check for empty clause *)ifC.comes_from_goalcthen()(* ignore, a valid lemma might contradict goal *)elseifC.is_emptyc&&Trail.is_empty(C.trailc)thenraise(Yield_falsec)elseifE.is_trivialcthen()else(trivial:=false;(* at least one clause does not simplify to [true] *)(* now make inferences with [c] and push non-trivial clauses to [q],
if needed *)if!n+2<max_steps_then(letnew_c=Iter.append(E.do_binary_inferencesc)(E.do_unary_inferencesc)innew_c|>Iter.filter_map(funnew_c->letnew_c,_=E.unary_simplifynew_cin(* discard trivial/conditional clauses, or clauses coming
from goals (as they might be true lemmas but contradict
the negated goal, which makes them even more useful);
also scan for empty clauses *)ifC.comes_from_goalnew_cthenNoneelseifnot(Trail.is_empty(C.trailnew_c))thenNoneelseifE.is_trivialnew_cthenNoneelseifC.is_emptynew_cthenraise(Yield_falsenew_c)elseSomenew_c)|>Iter.iter(func->ignore@@push_cc)))withNot_found->(* Due to orphan deletion a clause might not be found *)()done;Util.debugf~section2"@[<2>lemma @[%a@]@ apparently not absurd (after %d steps; trivial:%B)@]"(funk->kppg!n!trivial);if!trivialthenUtil.incr_statstat_trivial_lemmas;not!trivialwithYield_falsec->assert(C.is_emptyc);Util.debugf~section2"@[<2>lemma @[%a@] absurd:@ leads to empty clause %a (after %d steps)@]"(funk->kppgC.ppc!n);Util.incr_statstat_absurd_lemmas;falseletcheck_not_absurd_or_trivialg=ZProf.with_profprof_check_goalcheck_not_absurd_or_trivial_g(* some checks that [g] should be considered as a goal *)letis_acceptable_goal(g:t):bool=Util.incr_statstat_assess_goal;letres=test_goal_is_okg&&check_not_absurd_or_trivialginifresthenUtil.incr_statstat_assess_goal_ok;resmoduleFV=Cut_form.FV_tbl(structtypet=unitletcompare()()=0end)letadd_lemma,has_been_tried=lettbl=FV.create()inletaddf=FV.addtblf()andmem(g:t)=FV.memtbl(formg)inadd,memendmoduleT_view:sigtype'at=|T_varofT.var|T_dbofint|T_app_definedofID.t*Rewrite.Defined_cst.t*'alist|T_app_cstorofID.t*'alist|T_app_uninofID.t*'alist|T_appof'a*'alist|T_funofType.t*'a|T_builtinofBuiltin.t*'alistvalview:term->termtvalactive_subterms:term->termIter.t(** Visit all active subterms in the given term.
A subterm is active if it's under a cstor, uninterpreted symbol,
builtin, or under a defined function at an active position *)end=structtype'at=|T_varofT.var|T_dbofint|T_app_definedofID.t*Rewrite.Defined_cst.t*'alist|T_app_cstorofID.t*'alist|T_app_uninofID.t*'alist|T_appof'a*'alist|T_funofType.t*'a|T_builtinofBuiltin.t*'alistletview(t:term):termt=matchT.viewtwith|T.AppBuiltin(b,l)->T_builtin(b,l)|T.Varv->T_varv|T.ConstidwhenInd_ty.is_constructorid->T_app_cstor(id,[])|T.ConstidwhenClassify_cst.id_is_definedid->beginmatchRW.as_defined_cstidwith|Somec->T_app_defined(id,c,[])|None->T_app_unin(id,[])end|T.Constid->T_app_unin(id,[])|T.Fun(arg,bod)->T_fun(arg,bod)|T.App(f,l)->beginmatchT.viewfwith|T.ConstidwhenInd_ty.is_constructorid->T_app_cstor(id,l)|T.ConstidwhenClassify_cst.id_is_definedid->beginmatchRW.as_defined_cstidwith|Somec->T_app_defined(id,c,l)|None->T_app_unin(id,l)end|T.Constid->T_app_unin(id,l)|_->T_app(f,l)end|T.DBi->T_dbiletactive_subtermstyield:unit=letrecauxt=yieldt;beginmatchviewtwith|T_app_defined(_,c,l)->letpos=RW.Defined_cst.defined_positionscinassert(IArray.lengthpos>=List.lengthl);(* only look under active positions *)List.iteri(funisub->ifIArray.getposi=Defined_pos.P_activethenauxsub)l|T_fun(_,_)->()(* no induction under λ, we follow WHNF semantics *)|T_var_|T_db_->()|T_app(f,l)->auxf;List.iterauxl|T_app_cstor(_,l)->List.iterauxl|T_builtin(_,l)|T_app_unin(_,l)->List.iterauxlendinauxtend(* data flow for induction:
1) Introduce lemmas
- some lemmas come directly from the input, and are directly
asserted in Avatar
- some other lemmas are "guessed" from regular clauses that
contain inductive skolems. From these clauses (where we do not know
what to do with these skolems in general), a lemma is
built by negating the clauses and replacing the skolems by fresh
variables.
The goals obtained from this second source (given clauses)
are pre-processed:
- they are tested (see {!Test_prop}) to avoid trying to prove
trivially false lemmas
- they might be generalized using a collection of heuristics.
Each generalization is also tested.
Then, the surviving goals are added to Avatar using [A.introduce_cut].
2) new lemmas from Avatar (coming from 1)) are checked for {b variables}
with an inductive type.
For each such variable satisfying some side condition (e.g. occurring
at least once in active position), a fresh coverset of the variable's
type is built, and fresh skolems are created for the other variables.
Clauses of the goal (they share variables) are then instantiated
to the ground with these skolems (and each case of the coverset)
and then negated. We add a trail to them. The trail contains [not lemma]
and the corresponding case literal.
The resulting formulas are re-normalized into CNF and added to
the resulting set of clauses.
In addition, for each recursive case of the coverset, induction
hypothesis are added (instantiating the goal with this case,
keeping the other variables identical).
3) the resulting set of clauses
*)(* TODO: strong induction? instead of using sub-constants of the case
in the induction hypothesis, use a constraint [x < top] *)(** {2 Calculus of Induction} *)moduleMake(E:Env.S)(A:AVATARwithmoduleE=E)=structmoduleEnv=EmoduleCtx=E.CtxmoduleC=E.CmoduleBoolBox=BBoxmoduleBoolLit=BoolBox.LitmoduleGoal=Make_goal(E)letmax_depth=Env.flex_getk_ind_depthletcover_set_depth=Env.flex_getk_coverset_depthletis_ind_conjecture_c=matchC.distance_to_goalcwith|Some(0|1)->true|Some_|None->falselethas_pos_lit_c=CCArray.existsLiteral.is_pos(C.litsc)(* fresh var generator *)letfresh_var_gen_():Type.t->T.t=letr=ref0infunty->letv=T.var_of_int~ty!rinincrr;v(* scan terms for inductive skolems. *)letscan_terms~mode(seq:termIter.t):Ind_cst.ind_skolemlist=seq|>Iter.flat_mapInd_cst.find_ind_skolems|>Iter.filter(fun(id,_)->beginmatchInd_cst.id_as_cstid,modewith|None,_->true|Some_,`All->true|Somec,`No_sub_cst->(* do not generalize on sub-constants,
there are induction hypothesis on them that we will need *)not(Ind_cst.is_subc)end)|>Iter.to_rev_list|>CCList.sort_uniq~cmp:Ind_cst.ind_skolem_compare(* scan clauses for ground terms of an inductive type,
and perform induction on these terms.
@return a list of ways to generalize the given clause *)letscan_clause(c:C.t):Ind_cst.ind_skolemlistlist=letl1=ifE.flex_getk_ind_on_subcstthenC.litsc|>Lits.Seq.terms|>scan_terms~mode:`Allelse[]andl2=C.litsc|>Lits.Seq.terms|>scan_terms~mode:`No_sub_cstin(* remove duplicates, empty lists, etc. *)begin[l1;l2]|>CCList.sort_uniq~cmp:(CCList.compareInd_cst.ind_skolem_compare)|>List.filter(funl->not(CCList.is_emptyl))end(* goal for induction *)(* ensure the proper declarations are done for this coverset *)letdecl_cst_of_set(set:Cover_set.t):unit=Util.debugf~section3"@[<2>declare coverset@ `%a`@]"(funk->kCover_set.ppset);beginCover_set.declarationsset|>Iter.iter(fun(id,ty)->Ctx.declareidty)end(* induction on the given variables *)letind_on_vars(cut:A.cut_res)(vars:T.varlist):C.tlist=assert(vars<>[]);letg=A.cut_formcutinletdepth=A.cut_depthcutinletcut_blit=A.cut_litcutin(* proof step *)letproof=letproof_parent=A.cut_proof_parentcutinletinfos=UntypedAST.A.([app"induction"(List.map(funv->quoted(HVar.to_string_tstpv))vars)])inProof.Step.inference[proof_parent]~infos~rule:(Proof.Rule.mk"induction")~tags:[Proof.Tag.T_ind]in(* other variables -> become skolems *)letsubst_skolems:Subst.t=Cut_form.varsg|>(funset->T.VarSet.diffset(T.VarSet.of_listvars))|>T.VarSet.to_list|>List.map(funv->letty_v=HVar.tyvinletid=Ind_cst.make_skolemty_vinCtx.declareidty_v;(v,0),(T.const~ty:ty_vid,1))|>Subst.FO.of_list'?init:Nonein(* make cover-sets for the variables, for the {b skolemized} type *)letc_sets=List.map(funv->letty=Subst.Ty.applySubst.Renaming.nonesubst_skolems(HVar.tyv,0)inv,Cover_set.make~cover_set_depth~depthty)varsinList.iter(fun(_,set)->decl_cst_of_setset)c_sets;Util.debugf~section2"(@[<hv2>ind_on_vars (@[%a@])@ :form %a@ :cover_sets (@[<hv>%a@])@ :subst_skolem %a@])"(funk->k(Util.pp_listHVar.pp)varsCut_form.ppg(Util.pp_list(Fmt.Dump.pairHVar.ppCover_set.pp))c_setsSubst.ppsubst_skolems);(* set of boolean literal. We will add their exclusive disjonction to
the SAT solver. *)letb_lits=ref[]in(* build clauses for the induction on [v] *)letclauses=Util.map_productc_sets~f:(fun(v,set)->Cover_set.cases~which:`Allset|>Iter.to_list|>List.map(funcase->[v,case]))|>CCList.flat_map(fun(cases:(T.var*Cover_set.case)list)->assert(cases<>[]);(* literal for this case *)letb_lit_case=BBox.inject_case(List.mapsndcases)inCCList.Ref.pushb_litsb_lit_case;(* clauses [goal[v := t'] <- b_lit(case), ¬cut.blit]
for every [t'] sub-constant of [case] *)letpos_clauses=Util.seq_map_lcases~f:(fun(v,case)->Cover_set.Case.sub_constantscase|>CCList.filter_map(funsub_cst->(* only keep sub-constants that have the same type as [v] *)ifType.equal(Ind_cst.tysub_cst)(HVar.tyv)then(lett=Ind_cst.to_termsub_cstinSome(v,t))elseNone))|>Iter.flat_map_l(funv_and_t_list->letsubst=v_and_t_list|>List.map(fun(v,t)->(v,0),(t,1))|>Subst.FO.of_list'?init:Noneinletrenaming=Subst.Renaming.create()inletg'=Cut_form.apply_substrenamingsubst(g,0)inCut_form.csg'|>List.map(funlits->lettrail=[b_lit_case;BoolLit.negcut_blit;]|>Trail.of_listinC.create_alitsproof~trail~penalty:1))|>Iter.to_listin(* clauses [CNF[¬goal[case]) <- b_lit(case), ¬cut.blit] with
other variables being replaced by skolem symbols *)letneg_clauses=letsubst=cases|>List.map(fun(v,c)->(v,0),(Cover_set.Case.to_termc,1))|>Subst.FO.of_list'~init:subst_skolemsinletrenaming=Subst.Renaming.create()in(* for each clause, apply [subst] to it and negate its
literals, obtaining a DNF of [¬ And_i ctx_i[case]];
then turn DNF into CNF *)beginCut_form.apply_substrenamingsubst(g,0)|>Cut_form.cs|>Util.map_product~f:(funlits->letlits=Array.map(funl->[Literal.negatel])litsinArray.to_listlits)|>CCList.map(funl->letlits=Array.of_listlinlettrail=[BoolLit.negcut_blit;b_lit_case;]|>Trail.of_listinC.create_alitsproof~trail~penalty:1)endin(* all new clauses *)letres=List.rev_appendpos_clausesneg_clausesinUtil.debugf~section2"(@[<2>induction on (@[%a@])@ :form %a@ @[<2>:cases (@[%a@])@]@ \
:depth %d@ @[<2>:res [@[<hv>%a@]]@]@])"(funk->k(Util.pp_listHVar.pp)varsCut_form.ppg(Util.pp_listFmt.(pair~sep:(return":=@ ")HVar.ppCover_set.Case.pp))casesdepth(Util.pp_listC.pp)res);res)in(* FIXME: should do CNF here, too *)(* boolean constraint(s) *)letb_clauses=(* [\Or_{t in cases} b_lit(t)] *)letb_at_least_one=!b_lits(* for each case t!=u, [¬b_lit(t) ∨ ¬b_lit(u)] *)andb_at_most_one=CCList.diagonal!b_lits|>List.rev_map(fun(l1,l2)->[BoolLit.negl1;BoolLit.negl2])inb_at_least_one::b_at_most_oneinA.Solver.add_clauses~proofb_clauses;Util.debugf~section2"@[<2>add boolean constraints@ @[<hv>%a@]@ :proof %a@]"(funk->k(Util.pp_listBBox.pp_bclause)b_clausesProof.Step.ppproof);Util.incr_statstat_inductions;(* return the clauses *)clausestypedefined_path=|P_root|P_under_cstor|P_active|P_inactiveletdefined_path_add(p:defined_path)(pos:Defined_pos.t):defined_path=beginmatchp,poswith|(P_root|P_under_cstor|P_active),Defined_pos.P_active->P_active|(P_root|P_under_cstor|P_active),(Defined_pos.P_accumulator|Defined_pos.P_invariant)->P_inactive|P_inactive,_->P_inactiveendletsubterms_with_pos(f:Cut_form.t):(defined_path*Position.t*term)Iter.t=(* true if [x] occurs in active positions somewhere in [t] *)letrecaux(dp:defined_path)(p:Position.t)(t:term):_Iter.t=funk->k(dp,p,t);beginmatchT_view.viewtwith|T_view.T_app_defined(_,c,l)->letd_pos=RW.Defined_cst.defined_positionscinletlen=IArray.lengthd_posinassert(len>=List.lengthl);(* only look under active positions *)List.iteri(funiu->letd=IArray.getd_posiinaux(defined_path_adddpd)Position.(appendp@@arg(len-i-1)@@stop)uk)l|T_view.T_var_|T_view.T_db_->()|T_view.T_app(_,l)|T_view.T_app_unin(_,l)(* approx, we assume all positions are active *)|T_view.T_builtin(_,l)->letdp=defined_path_adddpDefined_pos.P_activeinletlen=List.lengthlinList.iteri(funiu->auxdpPosition.(appendp@@arg(len-i-1)@@stop)uk)l|T_view.T_fun(_,u)->letdp=defined_path_adddpDefined_pos.P_invariantinauxdpPosition.(appendp@@bodystop)uk|T_view.T_app_cstor(_,l)->letdp=matchdpwith|P_inactive->P_inactive|_->P_under_cstorinletlen=List.lengthlinList.iteri(funiu->auxdpPosition.(appendp@@arg(len-i-1)@@stop)uk)lendinCut_form.Seq.terms_with_pos~subterms:falsef|>Iter.flat_map(fun(t,pos)->auxP_rootpost)letterm_is_varxt:bool=matchT.viewtwith|T.Vary->HVar.equalType.equalxy|_->false(* active occurrences of [x] in [f] *)letvar_active_pos_seq(f:Cut_form.t)(x:T.var):_Iter.t=subterms_with_posf|>Iter.filter(function|P_active,_,t->term_is_varxt|_->false)(* does the variable occur in an active position in [f],
or under some uninterpreted position? *)letvar_occurs_under_active_pos(f:Cut_form.t)(x:T.var):bool=not(Iter.is_empty@@var_active_pos_seqfx)letvar_invariant_pos_seqfx:_Iter.t=subterms_with_posf|>Iter.filter(function|P_inactive,_,t->term_is_varxt|_->false)(* does the variable occur in a position that is invariant? *)letvar_occurs_under_invariant_pos(f:Cut_form.t)(x:T.var):bool=not(Iter.is_empty@@var_invariant_pos_seqfx)(* variable appears only naked, i.e. directly under [=] *)letvar_always_naked(f:Cut_form.t)(x:T.var):bool=Cut_form.csf|>Iter.of_list|>Iter.flat_mapIter.of_array|>Iter.for_all(function|Literal.Equation(l,r,_)->letcheck_tt=T.is_vart||not(T.var_occurs~var:xt)incheck_tl&&check_tr|Literal.True|Literal.False->true)letactive_subterms_form(f:Cut_form.t):T.tIter.t=Cut_form.csf|>Iter.of_list|>Iter.flat_mapIter.of_array|>Iter.flat_mapLiteral.Seq.terms|>Iter.flat_mapT_view.active_subtermsmoduleGeneralize:sigtypeform=Cut_form.ttypegeneralization=formlisttypet=form->generalizationlistvalid:t(** Do nothing *)valvars_at_active_pos:tvalterms_at_active_pos:tvalall:tend=structtypeform=Cut_form.ttypegeneralization=formlisttypet=form->generalizationlistletid_=[](* generalize on variables that occur both (several times) in active
positions, and which also occur (several times) in passive position.
The idea is that induction on the variable would work in active
positions, but applying induction hypothesis would fail because
of the occurrences in passive positions.
This should generalize [forall x. x + (x + x) = (x + x) + x]
into [forall x y. y + (x + x) = (y + x) + x]
*)letvars_at_active_pos(f:form):generalizationlist=letvars=Cut_form.varsf|>T.VarSet.to_list|>List.filter(funv->not(Type.is_tType(HVar.tyv))&&(Iter.length@@var_active_pos_seqfv>=2)&&(Iter.length@@var_invariant_pos_seqfv>=2))inbeginmatchvarswith|[]->[]|_->(* build a map to replace active occurrences of these variables by
fresh variables *)letm=letoffset=Cut_form.varsf|>T.VarSet.to_iter|>Iter.mapHVar.id|>Iter.max|>CCOpt.get_or~default:0|>succinCCList.foldi(funmiv->letv'=HVar.make~ty:(HVar.tyv)(i+offset)insubterms_with_posf|>Iter.filter_map(function|P_active,pos,twhenterm_is_varvt->Some(pos,T.varv')|_->None)|>Position.Map.add_iterm)Position.Map.emptyvarsinletf'=Cut_form.Pos.replace_manyfminUtil.debugf~section5"(@[<2>candidate_generalize@ :of %a@ :gen_to %a@ \
:by vars_active_pos :on (@[%a@])@ :map {@[%a@]}@])"(funk->kCut_form.ppfCut_form.ppf'(Util.pp_listHVar.pp)vars(Position.Map.ppPosition.ppTerm.pp)m);ifGoal.is_acceptable_goal@@Goal.of_cut_formf'then(Util.incr_statstat_generalize_vars_active_pos;[[f']])else[]end(* generalize non-variable subterms occurring several times
at active positions *)letterms_at_active_pos(f:form):generalizationlist=letrelevant_subterms=subterms_with_posf|>Iter.filter_map(function|P_active,pos,t->beginmatchT_view.viewtwith|T_view.T_app_unin(id,[])whenInd_cst.id_is_subid->None(* probably there because there are induction hyp. on it *)|_whenType.is_tType(T.tyt|>Type.returns)->None(* do not generalize on type or type constructors *)|(T_view.T_app_unin_|T_view.T_app_defined_)whenT.is_groundt->Some(pos,t)|_->Noneend|_->None)inletsubterms=relevant_subterms|>Iter.mapsnd|>Iter.group_by~hash:T.hash~eq:T.equal|>Iter.filter_map(function|t::_::_->Somet(* at least 2 *)|_->None)|>Iter.to_rev_listinbeginsubterms|>CCList.filter_map(funt->(* introduce variable for [t] *)letv=Cut_form.varsf|>T.VarSet.to_iter|>Iter.mapHVar.id|>Iter.max|>CCOpt.get_or~default:0|>succ|>HVar.make~ty:(T.tyt)inletm=relevant_subterms|>Iter.filter_map(function|pos,uwhenT.equaltu->Some(pos,T.varv)|_->None)|>Position.Map.of_iterinletf'=Cut_form.Pos.replace_manyfminUtil.debugf~section4"(@[<2>candidate_generalize@ :of %a@ :gen_to %a@ \
:by terms_active_pos@ :on %a@])"(funk->kCut_form.ppfCut_form.ppf'T.ppt);ifGoal.is_acceptable_goal@@Goal.of_cut_formf'then(Util.incr_statstat_generalize_terms_active_pos;Some[f'])elseNone)endletall=letg1=ifEnv.flex_getk_generalize_varthenvars_at_active_poselseidandg2=ifEnv.flex_getk_generalize_termthenterms_at_active_poselseidand(<++>)o(f,x)=matchowith|[]->fx|l->linfunf->g1f<++>(g2,f)end(* should we do induction on [x] in [c]? *)letshould_do_ind_on_var(f:Cut_form.t)(x:T.var):bool=not(E.flex_getk_limit_to_active)||var_occurs_under_active_posfx||var_always_nakedfxmoduleUF_vars=Avatar.UnionFind.Make(structtypekey=T.vartypevalue=T.varlistletequal=HVar.equalType.equallethash=HVar.hashletzero=[]letmerge=List.rev_appendend)leteq_var=HVar.equalType.equal(* group together variables that occur at active positions under
the same subterm *)letfind_var_clusters(f:Cut_form.t)(vars:T.varlist):T.varlistlist=letuf=UF_vars.create[]in(* add all variables of [f] *)T.VarSet.iter(funv->UF_vars.addufv[v])(Cut_form.varsf);(* naked variables together *)beginmatchCCList.find_pred(var_always_nakedf)varswith|None->()|Somev->assert(UF_vars.memufv);List.iter(funv'->assert(UF_vars.memufv');ifnot(HVar.equalType.equalvv')&&var_always_nakedfv'then(UF_vars.unionufvv';))vars;end;(* group variables naked in same (dis)equations *)beginCut_form.csf|>Iter.of_list|>Iter.flat_mapIter.of_array|>Iter.iter(function|Literal.Equation(l,r,_)->beginmatchT.viewl,T.viewrwith|T.Varx,T.Vary->UF_vars.unionufxy|_->()end|_->())end;(* other variables grouped by occurring at active pos in same subterm *)beginactive_subterms_formf|>Iter.iter(funt->matchT_view.viewtwith|T_view.T_app_defined(_,c,l)->letpos=RW.Defined_cst.defined_positionscinIter.of_listl|>Util.seq_zipi|>Iter.diagonal|>Iter.filter_map(fun((i1,t1),(i2,t2))->matchT.as_vart1,T.as_vart2with|Somex,Someywheni1<i2&&IArray.getposi1=Defined_pos.P_active&&IArray.getposi2=Defined_pos.P_active&¬(eq_varxy)&&CCList.mem~eq:eq_varxvars&&CCList.mem~eq:eq_varyvars->Some(x,y)|_->None)|>Iter.iter(fun(x,y)->assert(not(eq_varxy));UF_vars.unionufxy)|_->())end;letres=UF_vars.to_iteruf|>Iter.mapsnd|>Iter.filter_map(funvars->(* eliminate non-inductive variables *)letvars=List.filter(funv->Ind_ty.is_inductive_type@@HVar.tyv)varsinifvars=[]thenNoneelseSomevars)|>Iter.to_rev_listinUtil.debugf~section3"(@[<hv2>induction_clusters@ :in %a@ :clusters (@[<hv>%a@])@])"(funk->kCut_form.ppf(Util.pp_listFmt.(within"{""}"@@hvbox@@Util.pp_listHVar.pp))res);res(* proof by direct induction *)letprove_cut_by_ind(cut:A.cut_res):C.tlist=letg=A.cut_formcutinbeginmatchCut_form.ind_varsgwith|[]->[]|ivars->(* filter on which variables we do induction *)letivars=List.filter(funv->letok=should_do_ind_on_vargvinifnotokthen(Util.debugf~section3"(@[<hv>ind: inactive variable `%a`@ :in %a@])"(funk->kHVar.ppvCut_form.ppg););ok)ivarsinletclusters=find_var_clustersgivarsin(* for each variable, build a coverset of its type,
and do a case distinction on the [top] constant of this
coverset. *)CCList.flat_map(ind_on_varscut)clustersendletnew_lemma_=letn=ref0infun()->lets=Printf.sprintf"zip_lemma_%d"(CCRef.incr_then_getn)inUntypedAST.(A_app("name",[A_quoteds]))(* prove any lemma that has inductive variables. First we try
to generalize it, otherwise we prove it by induction *)letinductions_on_lemma(cut:A.cut_res):C.tlist=letg=A.cut_formcutinUtil.debugf~section4"(@[<hv>prove_lemma_by_induction@ %a@])"(funk->kCut_form.ppg);beginmatchGeneralize.allgwith|[]->prove_cut_by_indcut|new_goals_l->(* try each generalization in turn *)List.iter(funnew_goals->assert(new_goals<>[]);letg0=ginletnew_cuts=List.map(fung->A.introduce_cut~depth:(A.cut_depthcut)g(Proof.Step.lemma@@Proof.Src.internal[new_lemma_()])~reason:Fmt.(funout()->fprintfout"generalizing %a"Cut_form.ppg0))new_goalsinUtil.debugf~section4"(@[<2>@{<Yellow>generalize@}@ :lemma %a@ :into (@[<hv>%a@])@])"(funk->kCut_form.ppg(Util.pp_listCut_form.pp)new_goals);Util.incr_statstat_generalize;(* assert that the new goals imply the old one *)letproof=Proof.Step.trivialinA.add_implynew_cutscutproof;(* now prove the lemmas in Avatar *)List.iterA.add_lemmanew_cuts)new_goals_l;[]end(* replace the constants by fresh variables in the given clauses,
returning a goal. *)letgeneralize_clauses(cs:Lits.tlist)~(generalize_on:Ind_cst.ind_skolemlist):Goal.t=ifgeneralize_on=[]thenGoal.trivialelse((* offset to allocate new variables *)letoffset=Iter.of_listcs|>Iter.flat_mapLits.Seq.vars|>T.Seq.max_var|>succin(* (constant -> variable) list *)letpairs=List.mapi(funi(id,ty)->assert(not(Type.is_tType@@ty));T.const~tyid,T.var(HVar.make~ty(i+offset)))generalize_on|>T.Map.of_listinUtil.debugf~section5"@[<hv2>generalize_lits@ :in `@[<hv>%a@]`@ :subst (@[%a@])@]"(funk->k(Util.pp_list~sep:"∧"Lits.pp)cs(T.Map.ppT.ppT.pp)pairs);(* replace skolems by the new variables, then negate the formula
and re-CNF the negation.
Purification constraints are kept as hypotheses in each resulting clause. *)begincs|>Util.map_product~f:(funlits->letlits_l=Array.to_listlitsin(* separate the guard (constraints) from other literals *)letguard,other_lits=List.partitionLiteral.is_constraintlits_linletreplace_lits=List.map(Literal.map(funt->T.replace_mtpairs))inletguard=replace_litsguardinletother_lits=replace_litsother_litsinList.map(funother_lit->Literal.negateother_lit::guard)other_lits)|>List.mapArray.of_list|>Goal.of_formend)(* try to prove these clauses by turning the given constants into
variables, negating the clauses, and introducing the result
as a lemma to be proved by induction.
@param generalize_on the set of (skolem) constants that are replaced
by free variables in the negation of [clauses] *)letprove_by_ind(clauses:C.tlist)~ignore_depth~generalize_on:unit=letpp_csts=Util.pp_listFmt.(pair~sep:(return":@ ")ID.ppType.pp)in(* remove trivial clauses *)letclauses=List.filter(func->not@@Literals.is_trivial@@C.litsc)clausesinUtil.debugf~section5"(@[<2>consider_proving_by_induction@ \
:clauses [@[%a@]]@ :generalize_on (@[%a@])@]"(funk->k(Util.pp_listC.pp)clausespp_cstsgeneralize_on);letdepth=Iter.of_listgeneralize_on|>Iter.map(fun(id,_)->Ind_cst.ind_skolem_depthid)|>Iter.max|>CCOpt.get_or~default:0(* the trail should not contain a positive "lemma" atom.
We accept negative lemma atoms because the induction can be used to
prove the lemma *)andno_pos_lemma_in_trail()=Iter.of_listclauses|>Iter.mapC.trail|>Iter.flat_mapTrail.to_iter|>Iter.for_all(funlit->not(BoolLit.signlit&&BBox.is_lemmalit))inif(ignore_depth||depth<max_depth)&&no_pos_lemma_in_trail()then(letgoal=generalize_clauses(List.mapC.litsclauses)~generalize_on|>Goal.simplifyinletgoals=Goal.splitgoalinList.iter(fungoal->(* check if goal is worth the effort and if it's new *)ifGoal.has_been_triedgoalthen(Util.debugf~section1"(@[<2>goal_already_active@ %a@])"(funk->kGoal.ppgoal);Util.incr_statstat_goal_duplicate;())elseifGoal.is_acceptable_goalgoalthen(Util.debugf~section1"(@[<2>@{<green>prove_by_induction@}@ :clauses (@[%a@])@ :goal %a@])"(funk->k(Util.pp_listC.pp)clausesGoal.ppgoal);letproof=Proof.Step.lemma@@Proof.Src.internal[new_lemma_()]in(* new lemma has same penalty as the clauses *)letpenalty=List.fold_left(funnc->n+C.penaltyc)0clausesinletcut=A.introduce_cut~penalty~depth(Goal.formgoal)proof~reason:Fmt.(funout()->fprintfout"(@[prove_ind@ :clauses (@[%a@])@ :on (@[%a@])@])"(Util.pp_listC.pp)clausespp_cstsgeneralize_on)inA.add_lemmacut))goals);()(* Try to prove the given clause by introducing an inductive lemma. *)letinf_prove_by_ind(c:C.t):C.tlist=List.iter(funconsts->assert(consts<>[]);prove_by_ind[c]~ignore_depth:false~generalize_on:consts)(scan_clausec);[](* hook for converting some statements to clauses.
It check if [Negated_goal l] contains clauses with inductive skolems,
in which case it tries to prove these clauses by induction in a lemma.
*)letconvert_statementst=beginmatchStatement.viewstwith|Statement.NegatedGoal(skolems,_)->(* find inductive skolems in there *)letind_skolems=List.filter(fun(id,ty)->Ind_cst.id_is_ind_skolemidty)skolemsinbeginmatchind_skolemswith|[]->E.CR_skip|consts->(* introduce one lemma where all the skolems are
replaced by variables. But first, simplify these clauses
because otherwise the inductive subgoals
(which are simplified) will not match
the inductive hypothesis.
NOTE: do not use {!all_simplify} as it interacts badly
with avatar splitting. *)letclauses=C.of_statementst|>List.map(func->fst(E.basic_simplifyc))inprove_by_indclauses~ignore_depth:true~generalize_on:consts;(* "skip" in any case, because the proof is done in a cut anyway *)E.CR_skipend|_->E.cr_skipend(* checks whether the trail is trivial, that is, it contains
two literals [i = t1] and [i = t2] with [t1], [t2] distinct cover set cases *)lettrail_is_trivial_casestrail=letseq=Trail.to_itertrailin(* all boolean literals that express paths *)letrelevant_cases=Iter.filter_mapBoolBox.as_caseseqin(* are there two distinct incompatible cases in the trail? *)Iter.diagonalrelevant_cases|>Iter.exists(fun(c1,c2)->letres=not(List.lengthc1=List.lengthc2)||not(CCList.equalCover_set.Case.equalc1c2)inifresthen(Util.debugf~section4"(@[<2>trail@ @[%a@]@ is trivial because of@ \
{@[(@[%a@]),@,(@[%a@])}@]@])"(funk->kC.pp_trailtrail(Util.pp_listCover_set.Case.pp)c1(Util.pp_listCover_set.Case.pp)c2));res)(* make trails with several lemmas in them trivial, so that we have to wait
for a lemma to be proved before we can use it to prove another lemma *)lettrail_is_trivial_lemmastrail=letseq=Trail.to_itertrailin(* all boolean literals that express paths *)letrelevant_cases=seq|>Iter.filter_map(funlit->BoolBox.as_lemmalit|>CCOpt.map(funlemma->lemma,BoolLit.signlit))in(* are there two distinct positive lemma literals in the trail? *)Iter.diagonalrelevant_cases|>Iter.exists(fun((c1,sign1),(c2,sign2))->letres=sign1&&sign2&¬(Cut_form.equalc1c2)inifresthen(Util.debugf~section4"(@[<2>trail@ @[%a@]@ is trivial because of lemmas@ \
{@[(@[%a@]),@,(@[%a@])}@]@])"(funk->kC.pp_trailtrailCut_form.ppc1Cut_form.ppc2););res)(* look whether, to prove the lemma, we need induction *)letprove_lemma_by_indcut=letl=inductions_on_lemmacutinifl<>[]then(Util.incr_statstat_lemmas;E.CR_returnl)elseE.CR_skipletregister()=Util.debug~section2"register induction";letd=Env.flex_getk_ind_depthinUtil.debugf~section2"maximum induction depth: %d"(funk->kd);Env.add_unary_inf"induction.ind"inf_prove_by_ind;Env.add_clause_conversionconvert_statement;Env.add_is_trivial_trailtrail_is_trivial_cases;ifE.flex_getAvatar.k_simplify_trailthen(Env.add_is_trivial_trailtrail_is_trivial_lemmas;);(* try to prove lemmas by induction *)A.add_prove_lemmaprove_lemma_by_ind;()endletenabled_=reftrueletdepth_=ref4(* NOTE: should be 3? *)letlimit_to_active=reftrueletcoverset_depth=ref1letgoal_assess_limit=ref8letind_sub_cst=reftrueletgen_var=reftrueletgen_term=reftrue(* if induction is enabled AND there are some inductive types,
then perform some setup after typing, including setting the key
[k_enable].
It will update the parameters. *)letpost_typing_hookstmtsstate=(* only enable if there are inductive types *)letshould_enable=CCVector.exists(funst->matchStatement.viewstwith|Statement.Data_->true|_->false)stmtsinif!enabled_&&should_enablethen(Util.debug~section1"Enable induction";state|>Flex_state.addk_enabletrue|>Flex_state.addk_ind_depth!depth_|>Flex_state.addk_limit_to_active!limit_to_active|>Flex_state.addk_coverset_depth!coverset_depth|>Flex_state.addk_goal_assess_limit!goal_assess_limit|>Flex_state.addk_ind_on_subcst!ind_sub_cst|>Flex_state.addk_generalize_var!gen_var|>Flex_state.addk_generalize_term!gen_term|>Flex_state.addCtx.Key.lost_completenesstrue)elseFlex_state.addk_enablefalsestate(* if enabled: register the main functor, with inference rules, etc. *)letenv_action(moduleE:Env.S)=letis_enabled=E.flex_getk_enableinifis_enabledthen(let(moduleA)=Avatar.get_env(moduleE)in(* XXX here we do not use E anymore, because we do not know
that A.E = E. Therefore, it is simpler to use A.E. *)letmoduleE=A.EinE.Ctx.lost_completeness();E.Ctx.set_selection_funSelection.no_select;letmoduleM=Make(A.E)(A)inM.register();)letextension=Extensions.({defaultwithname="induction_simple";post_typing_actions=[post_typing_hook];env_actions=[env_action];})let()=Params.add_opts["--induction",Options.switch_settrueenabled_," enable induction";"--no-induction",Options.switch_setfalseenabled_," disable induction";"--induction-depth",Arg.Set_intdepth_," maximum depth of nested induction";"--ind-only-active-pos",Arg.Setlimit_to_active," limit induction to active positions";"--no-ind-only-active-pos",Arg.Clearlimit_to_active," limit induction to active positions";"--ind-coverset-depth",Arg.Set_intcoverset_depth," coverset depth in induction";"--ind-goal-assess",Arg.Set_intgoal_assess_limit," number of steps for assessing potential lemmas";"--ind-sub-cst",Arg.Setind_sub_cst," do induction on sub-constants";"--no-ind-sub-cst",Arg.Clearind_sub_cst," do not do induction on sub-constants";"--ind-gen-var",Arg.Setgen_var," generalize on variables";"--ind-gen-term",Arg.Setgen_term," generalize on terms";"--no-ind-gen-var",Arg.Cleargen_var," do not generalize on variables";"--no-ind-gen-term",Arg.Cleargen_term," do not generalize on terms"];Params.add_to_modes["ho-complete-basic";"ho-pragmatic";"ho-competitive";"fo-complete-basic";"lambda-free-intensional";"lambda-free-extensional";"ho-comb-complete";"lambda-free-purify-intensional";"lambda-free-purify-extensional"](fun()->enabled_:=false);