123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764(**************************************************************************)(* This file is part of the Codex semantics library. *)(* *)(* Copyright (C) 2013-2025 *)(* CEA (Commissariat à l'énergie atomique et aux énergies *)(* alternatives) *)(* *)(* you can redistribute it and/or modify it under the terms of the GNU *)(* Lesser General Public License as published by the Free Software *)(* Foundation, version 2.1. *)(* *)(* It is distributed in the hope that it will be useful, *)(* but WITHOUT ANY WARRANTY; without even the implied warranty of *)(* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *)(* GNU Lesser General Public License for more details. *)(* *)(* See the GNU Lesser General Public License version 2.1 *)(* for more details (enclosed in the file LICENSE). *)(* *)(**************************************************************************)(* BDDs and MTBDDs. *)moduleHashtblPair(T:Hashtbl.HashedType)=structmodulePair=structtypet=T.t*T.tletequal(a1,a2)(b1,b2)=T.equala1b1&&T.equala2b2lethash(a1,a2)=Hashing.hash2(T.hasha1)(T.hasha2)endincludeHashtbl.Make(Pair)end(* let cache_default_size = (\* 7001 *\) 251 *)letweakhash_default_size=(* 7001 *)251moduleMake(Var:sigtypetvalequal:t->t->bool(* Variable order in the bdd: smallest variables are closer to the
root. *)valcompare:t->t->intvalhash:t->intvalpretty:Format.formatter->t->unitend)=struct(* Hash-consing tag. *)typetag=intmoduleBDD=struct(* This module sealing ensures that bdds are always hash-consed in
the rest of the code. *)moduleType:sigtypebdd=privateZero|One|Ifoftag*Var.t*bdd*bddtypet=bddvalequal:bdd->bdd->boolvalhash:bdd->intvalcompare:bdd->bdd->intvalzero:bddvalone:bddvalmk:Var.t->bdd->bdd->bddend=struct(* Phantom type to track hash-consing, but this is cumbersome to
use; especially this leaks to the interface of the
module. The private type + encapsulation suffices for outside
of [Type]; uncomment "Interned of" just to check conformity
inside [Type]. *)[@@@ocaml.warning"-34"]type'ainterned=(* Interned of *)'a[@@@ocaml.warning"+34"]typebdd(* _uninterned *)=Zero|One|Ifoftag*Var.t*bdd*bdd(* and bdd = bdd_uninterned interned *)typet=bdd(* Equality and hashing when the bdd is hash-consed. *)(* let equal = fun (Interned a) (Interned b) -> a (==) b;; *)letequal=(==);;lethash((* Interned *)x)=matchxwith|Zero->0|One->1|If(tag,_,_,_)->tag;;letcompareab=Stdlib.compare(hasha)(hashb)moduleWeakHash=Weak.Make(struct(* Equality and hashing when the bdd is not yet hash-consed (but
sub-objects are). *)typet=bdd(* _uninterned *)letequalab=match(a,b)with|_,_whena==b->true|If(_,vara,bdda1,bdda2),If(_,varb,bddb1,bddb2)->bdda1==bddb1&&bdda2==bddb2&&Var.equalvaravarb|_,_->falselethash=function|Zero->0|One->1(* Note: uses [hash] for the hash-consed bdds; there is no recursive call to [hash]!. *)|If(_,var,left,right)->Hashing.hash3(Var.hashvar)(hashleft)(hashright)end);;(* Tag of Zero and One are 0 and 1. *)lettag_ref=ref2;;lettable=WeakHash.createweakhash_default_size;;letzero=(* Interned *)Zeroletone=(* Interned *)Oneletmkvarthen_else_=ifthen_==else_thenthen_elselettentative=If(!tag_ref,var,then_,else_)inletret=WeakHash.mergetabletentativeinifret==tentativethenincrtag_ref;(* Interned *)ret;;endincludeType;;(* Naive version; more efficient one should use lets to avoid
combinatory explosion. Note that pretty-printing bdds is
reserved to debugging anyway. *)letrecprettyfmtbdd=matchbddwith|Zero->Format.fprintffmt"0"|One->Format.fprintffmt"1"|If(_,var,bdd1,bdd2)->Format.fprintffmt"@[<v>@[<hv 2> %a ->@ %a@]@\n@[<hv 2>!%a ->@ %a@]@]"Var.prettyvarprettybdd1Var.prettyvarprettybdd2;;letvarv=mkvonezero;;moduleHash1=Hashtbl.Make(Type);;moduleHash2=HashtblPair(Type);;moduleVar_Hash=Hashtbl.Make(Var)(* Computes the size of the BDD (number of nodes in the dag, and
number of boolean variables). Useful for debugging. *)let_size_ofbdd=letseen_dags=Hash1.create97inletseen_vars=Var_Hash.create97inletcount_vars=ref0inletrecsizebdd=ifHash1.memseen_dagsbddthen0elsematchbddwith|Zero|One->1|If(_,v,then_,else_)->ifnot(Var_Hash.memseen_varsv)then(Var_Hash.replaceseen_varsv();incrcount_vars);sizethen_+sizeelse_inletres=sizebddin(res,!count_vars);;moduleWithCache(Param:sigvalcache_default_size:intend)=struct(* MAYBE: we could create the cache to handle commutativity;
i.e. a && b == b && a. This translates to equality of the
pairs (a,b) and (b,a) in the Hash2 table. I am not sure yet,
but probably, this would provide a performance gain (fewer
hashtbl entries, more hits). To do this, we would need a
apply_commutative function, that would call
Hash2_commutative. It suffice to sort the arguments by their tag. *)(* MAYBE: Use weak hash instead of these hashes. *)letnot_cache=Hash1.createParam.cache_default_size;;letand_cache=Hash2.createParam.cache_default_size;;letor_cache=Hash2.createParam.cache_default_size;;letimp_cache=Hash2.createParam.cache_default_size;;letrec(!~)x=tryHash1.findnot_cachexwithNot_found->letres=matchxwith|Zero->one|One->zero|If(_,v,then_,else_)->Type.mkv(!~then_)(!~else_)inHash1.addnot_cachexres;res;;(* The different arguments are the different partial applications of
the binary operation [opxy] when one of the two arguments x or y is
known to be 0 or 1. *)letapplycacheop0yop1yopx0opx1xy=(* TODO: Benchmark to test if the cache should be shared betwween invocations. *)(* let cache = Hash2.create Param.cache_default_size in *)(* let (sizex,varsx) = size_of x and (sizey,varsy) = size_of y in *)(* Kernel.feedback "sizex %d varsx %d sizey %d varsy %d" sizex varsx sizey varsy; *)letrecapplyu1u2=letu12=(u1,u2)intryHash2.findcacheu12withNot_found->letres=matchu12with|Zero,_->op0yu2|One,_->op1yu2|_,Zero->opx0u1|_,One->opx1u1|If(_,v1,then1,else1),If(_,v2,then2,else2)->(matchVar.comparev1v2with|0->Type.mkv1(applythen1then2)(applyelse1else2)|xwhenx<0->Type.mkv1(applythen1u2)(applyelse1u2)|_->Type.mkv2(applyu1then2)(applyu1else2))inHash2.replacecacheu12res;resinapplyxy;;(* There are only 4 boolean functions of one argument. *)letf_one_=one;;letf_zero_=zero;;letf_samex=xletf_inv=(!~)(* Hopefully everything is inlined and this is very efficient... *)let(&&~)=applyand_cachef_zerof_samef_zerof_same;;let(||~)=applyor_cachef_samef_onef_samef_one;;let(==>~)=applyimp_cachef_onef_samef_invf_oneendincludeWithCache(structletcache_default_size=17end);;end(****************************************************************)(* MTBDDs: functions from boolean variables to arbitrary
terminals. *)(* Note: it is possible to have an implementation not to hash-cons
Terminal of ...; but in this case one cannot use (==) for
mtbdds. *)moduletypeTerminal=sigtypetvalequal:t->t->boolvalhash:t->intvalpretty:Format.formatter->t->unitendtype'amtbdd=Terminaloftag*'a|Ifoftag*Var.t*'amtbdd*'amtbddmoduletypeMTBDD=sigmoduleTerminal:Terminaltypet=Terminal.tmtbddvalequal:t->t->boolvalhash:t->intvalpretty:Format.formatter->t->unitvalterminal:Terminal.t->tvalmk:Var.t->t->t->tendletmap1(typea)(moduleMa:MTBDDwithtypeTerminal.t=a)(typeres)(moduleMres:MTBDDwithtypeTerminal.t=res)f=letmoduleHash1=Hashtbl.Make(Ma)inletmap1_cache=Hash1.create251inletrecmap1mtbdd=tryHash1.findmap1_cachemtbddwithNot_found->letres=matchmtbddwith|Terminal(_,t)->Mres.terminal(ft)|If(_,v,then_,else_)->Mres.mkv(map1then_)(map1else_)inHash1.replacemap1_cachemtbddres;resinmap1;;modulePair(M1:Hashtbl.HashedType)(M2:Hashtbl.HashedType)=structtypet=M1.t*M2.tletequal(a1,a2)(b1,b2)=M1.equala1b1&&M2.equala2b2;;lethash(a1,a2)=65533*(M1.hasha1)+M2.hasha2endletmap2(typea)(moduleMa:MTBDDwithtypeTerminal.t=a)(typeb)(moduleMb:MTBDDwithtypeTerminal.t=b)(typeres)(moduleMres:MTBDDwithtypeTerminal.t=res)f=letmoduleHash2=Hashtbl.Make(Pair(Ma)(Mb))inletmap2_cache=Hash2.create251in(* Share cache between applications of map1 etc. *)letmap1a=map1(moduleMa)(moduleMres)inletmap1b=map1(moduleMb)(moduleMres)inletrecmap2mamb=letmab=(ma,mb)intryHash2.findmap2_cachemabwithNot_found->letres=matchmabwith|Terminal(_,t),_->map1b(ft)mb|_,Terminal(_,t)->map1a(funx->fxt)ma|If(_,va,thena,elsea),If(_,vb,thenb,elseb)->(matchVar.comparevavbwith|0->Mres.mkva(map2thenathenb)(map2elseaelseb)|xwhenx<0->Mres.mkva(map2thenamb)(map2elseamb)|_->Mres.mkvb(map2mathenb)(map2maelseb))inHash2.replacemap2_cachemabres;resinmap2;;moduleTriple(M1:Hashtbl.HashedType)(M2:Hashtbl.HashedType)(M3:Hashtbl.HashedType)=structtypet=M1.t*M2.t*M3.tletequal(a1,a2,a3)(b1,b2,b3)=M1.equala1b1&&M2.equala2b2&&M3.equala3b3;;lethash(a1,a2,a3)=65533*(M1.hasha1)+257*M2.hasha2+M3.hasha3endletmap3(typea)(moduleMa:MTBDDwithtypeTerminal.t=a)(typeb)(moduleMb:MTBDDwithtypeTerminal.t=b)(typec)(moduleMc:MTBDDwithtypeTerminal.t=c)(typeres)(moduleMres:MTBDDwithtypeTerminal.t=res)f=letmoduleHash3=Hashtbl.Make(Triple(Ma)(Mb)(Mc))inletmap3_cache=Hash3.create251inletmap2ab=map2(moduleMa)(moduleMb)(moduleMres)inletmap2ac=map2(moduleMa)(moduleMc)(moduleMres)inletmap2bc=map2(moduleMb)(moduleMc)(moduleMres)inletrecmap3mambmc=letmabc=(ma,mb,mc)intryHash3.findmap3_cachemabcwithNot_found->letres=matchmabcwith|Terminal(_,t),_,_->map2bc(ft)mbmc|_,Terminal(_,t),_->map2ac(funac->fatc)mamc|_,_,Terminal(_,t)->map2ab(funab->fabt)mamb|If(_,va,thena,elsea),If(_,vb,thenb,elseb),If(_,vc,thenc,elsec)->(matchVar.comparevavbwith|0->beginmatchVar.comparevbvcwith|0->Mres.mkva(map3thenathenbthenc)(map3elseaelsebelsec)|xwhenx<0->Mres.mkva(map3thenathenbmc)(map3elseaelsebmc)|_->Mres.mkvc(map3mambthenc)(map3mambelsec)end|xwhenx<0->beginmatchVar.comparevavcwith|0->Mres.mkva(map3thenambthenc)(map3elseambelsec)|xwhenx<0->Mres.mkva(map3thenambmc)(map3elseambmc)|_->Mres.mkvc(map3mambthenc)(map3mambelsec)end|_->beginmatchVar.comparevbvcwith|0->Mres.mkvb(map3mathenbthenc)(map3mathenbelsec)|xwhenx<0->Mres.mkvb(map3mathenbmc)(map3maelsebmc)|_->Mres.mkvc(map3mambthenc)(map3mambelsec)end)in(* Kernel.feedback "map3 ma %a@\n mb %a@\n mc %a@\n mres %a" *)(* Ma.pretty ma Mb.pretty mb Mc.pretty mc Mres.pretty res; *)Hash3.replacemap3_cachemabcres;resinmap3;;moduleMTBDD_Make(Terminal:Terminal)=structmoduleType:sigtypet=Terminal.tmtbddvalequal:t->t->boolvalhash:t->intvalcompare:t->t->intvalterminal:Terminal.t->tvalmk:Var.t->t->t->tend=structtypet=Terminal.tmtbddletequal=(==)(* To avoid collision between terminals and non-terminals, I use
impair values for non-terminals (stored in the tag) and pair
ones for terminals (stored, or re-computed in the
Terminal). *)(* Note: we could avoid storing the tag in the terminal by just returning
[2 * Terminal.hash t] here; however this is faster. *)lethash=function|Terminal(tag,t)->tag|If(tag,_,_,_)->tag;;letcompareab=Stdlib.compare(hasha)(hashb);;[@@@warning"-8"](* We use 2 different hashes for terminal and non-terminal
terms; this avoids spurious collisions in the table. MAYBE:
Use GADT to avoid testing the cases of the Sum. *)moduleTerminalHash=Weak.Make(structtypet=Terminal.tmtbddletequal(Terminal(_,a))(Terminal(_,b))=Terminal.equalab;;lethash(Terminal(_,a))=Terminal.hashaend)moduleIfHash=Weak.Make(structtypet=Terminal.tmtbddletequal(If(_,var1,then1,else1))(If(_,var2,then2,else2))=Var.equalvar1var2&&then1==then2&&else1==else2lethash(If(_,var,then_,else_))=Hashing.hash3(Var.hashvar)(hashthen_)(hashelse_);;end)[@@@warning"+8"]lettag_ref=ref1;;letterminal_table=TerminalHash.createweakhash_default_size;;letif_table=IfHash.createweakhash_default_size;;letterminal_tag_ref=ref2;;letterminalx=lettentative=Terminal(!terminal_tag_ref,x)inletret=TerminalHash.mergeterminal_tabletentativein(ifret==tentativethen(terminal_tag_ref:=!terminal_tag_ref+2));ret;;letmkvarthen_else_=ifequalthen_else_thenthen_elselettentative=If(!tag_ref,var,then_,else_)inletret=IfHash.mergeif_tabletentativein(ifret==tentativethen(tag_ref:=!tag_ref+2));ret;;(* Note: a drawback of this implementation is that if Terminal is
already hash-consed, we perform a double hash-consing. We could
avoid hashconsing Terminal(t) terms; but in this case mtbdds
would provide a equal function that is not physical
equality. An advantage of this would be the possibility to
provide a functional, and not functorial, interface to MTBDDs,
that would only require that == works on terminals. *)endincludeType(* MAYBE: Use a weak key hash, and keep the hash while the bdds
exist. *)moduleHash1=Hashtbl.Make(Type);;moduleHash2=HashtblPair(Type);;(* Note: the caches here are per-application. They could be shared
accross different applications, but there should be a different
cache for each f function. *)letmap1f=letmap1_cache=Hash1.create251inletrecmap1mtbdd=tryHash1.findmap1_cachemtbddwithNot_found->letres=matchmtbddwith|Terminal(_,t)->terminal(ft)|If(_,v,then_,else_)->mkv(map1then_)(map1else_)inHash1.replacemap1_cachemtbddres;resinmap1;;letmap2f=letmap2_cache=Hash2.create251inletrecmap2bdd1bdd2=letbdd12=(bdd1,bdd2)intryHash2.findmap2_cachebdd12withNot_found->letres=matchbdd12with|Terminal(_,t),_->map1(ft)bdd2|_,Terminal(_,t)->map1(funx->fxt)bdd1|If(_,v1,then1,else1),If(_,v2,then2,else2)->(matchVar.comparev1v2with|0->mkv1(map2then1then2)(map2else1else2)|xwhenx>0->mkv2(map2bdd1then2)(map2bdd2else2)|_->mkv1(map2then1bdd2)(map2else1bdd2))inHash2.replacemap2_cachebdd12res;resinmap2;;letallpred=letcache=Hash1.create251inletrecallx=tryHash1.findcachexwithNot_found->letres=matchxwith|Terminal(_,t)whenpredt->BDD.one|Terminal(_,t)->BDD.zero|If(_,v,then_,else_)->BDD.mkv(allthen_)(allelse_)inHash1.replacecachexres;resinall;;(* Naive version; more efficient one should use lets to avoid
combinatory explosion. Note that pretty-printing bdds is
reserved to debugging anyway. *)letrecprettyfmtbdd=matchbddwith|Terminal(_,t)->Terminal.prettyfmtt|If(_,var,bdd1,bdd2)->Format.fprintffmt"@[<v>@[<hv 2> %a ->@ %a@]@\n@[<hv 2>!%a ->@ %a@]@]"Var.prettyvarprettybdd1Var.prettyvarprettybdd2moduleWith_Set(TerminalSet:sigtypetvalempty:tvalsingleton:Terminal.t->tvalunion:t->t->t(* val add: Terminal.t -> t -> t
* val pretty: Format.formatter -> t -> unit *)end)=structmoduleHash_MTBDD_BDD=Hashtbl.Make(structtypet=Terminal.tmtbdd*BDD.tletequal(a1,a2)(b1,b2)=Type.equala1b1&&BDD.equala2b2lethash(a1,a2)=Hashing.hash2(Type.hasha1)(BDD.hasha2)end)(* Like in other places, this should maybe be a weak key hash. *)letfind_all_cache=Hash1.create251;;letfind_cache=Hash_MTBDD_BDD.create251;;(* Note: these are all specific version of apply/map2, with
heterogeneous mtbdds; a bdd being a boolean mtbdd. *)(* Faster version: no need to test the BDD. *)letrecfind_allx=tryHash1.findfind_all_cachexwithNot_found->letres=matchxwith|Terminal(_,t)->TerminalSet.singletont|If(_,_,then_,else_)->TerminalSet.union(find_allthen_)(find_allelse_)inHash1.replacefind_all_cachexres;res;;(* Note: in the following, Zero means "no valuation" and One
"all valuations". *)letrecfindmtbddbdd=(* Kernel.feedback "find mtbdd %a bdd %a" pretty mtbdd BDD.pretty bdd; *)letpair=(mtbdd,bdd)intryHash_MTBDD_BDD.findfind_cachepairwithNot_found->letres=matchbddwith|BDD.Zero->TerminalSet.empty|BDD.One->find_allmtbdd|BDD.If(_,var2,then2,else2)->(matchmtbddwith|Terminal(_,t)->TerminalSet.singletont|If(_,var1,then1,else1)->(matchVar.comparevar1var2with|0->TerminalSet.union(findthen1then2)(findelse1else2)|xwhenx<0->TerminalSet.union(findthen1bdd)(findelse1bdd)|_->TerminalSet.union(findmtbddthen2)(findmtbddelse2)))inHash_MTBDD_BDD.replacefind_cachepairres;(* Kernel.feedback "find res: %a" TerminalSet.pretty res; *)res;;(* As it is, the cache is a memory leak; this version clears the
cache between invocations. TODO: Benchmarks should tell what
is the best version. *)(* let find mtbdd bdd = *)(* Hash1.clear find_all_cache; *)(* Hash_MTBDD_BDD.clear find_cache; *)(* find mtbdd bdd *)(* ;; *)(* Note: it may not be necessary to share add_cache between
invocations of add. The cache has three entry parameters,
but the terminal to add need to be looked up only once. *)(* let add_cache: mtbdd Hash_MTBDD_BDD.t Hash_Terminal.t = Hash_Terminal.create 251;; *)(* For valuations of bdd, set to t2; else set to t1. Also fill
the cache when the bdd changes. *)letrecadd_singleadd_cachet1bddt2singleton=tryHash_MTBDD_BDD.findadd_cache(t1,bdd)withNot_found->letres=matchbddwith|BDD.Zero->t1|BDD.One->Hash_MTBDD_BDD.replacefind_cache(t2,bdd)singleton;t2|BDD.If(_,var,then_,else_)->letres=mkvar(add_singleadd_cachet1then_t2singleton)(add_singleadd_cachet1else_t2singleton)inHash_MTBDD_BDD.replacefind_cache(res,bdd)singleton;resinHash_MTBDD_BDD.replaceadd_cache(t1,bdd)res;res;;letaddmtbddbddt=letterminal=terminaltinletsingleton=TerminalSet.singletontinletadd_cache=(* TODO: Should we share add_cache across invocations of
add? Probably not for my current use cases. *)(* try Hash_Terminal.find add_cache t *)(* with Not_found -> *)letnew_cache=Hash_MTBDD_BDD.create17in(* Hash_Terminal.replace add_cache t new_cache; *)new_cacheinletrecaddmtbddbdd=tryHash_MTBDD_BDD.findadd_cache(mtbdd,bdd)withNot_found->letres=matchbddwith|BDD.Zero->mtbdd|BDD.One->(Hash_MTBDD_BDD.replacefind_cache(terminal,bdd)singleton;terminal)|BDD.If(_,var2,then2,else2)->letres=(matchmtbddwith|Terminal(_,t2)->add_singleadd_cachemtbddbddterminalsingleton|If(_,var1,then1,else1)->(matchVar.comparevar1var2with|0->mkvar1(addthen1then2)(addelse1else2)|xwhenx<0->mkvar1(addthen1bdd)(addelse1bdd)|_->mkvar2(addmtbddthen2)(addmtbddelse2)))inHash_MTBDD_BDD.replacefind_cache(res,bdd)singleton;resinHash_MTBDD_BDD.replaceadd_cache(mtbdd,bdd)res;res(* Pre-fill the cache in the likely case of a further call to find. *)(* Note: it is important not to fill the cache for the
BDD.Zero case; as find with a BDD of Zero should bottom,
and not the singleton. *)inaddmtbddbdd;;(* Simplified version: we know that BDD is one, and do not need to test it. *)letrecupdate_alladd_cachemtbddf=tryHash_MTBDD_BDD.findadd_cache(mtbdd,BDD.one)withNot_found->letres=matchmtbddwith|Terminal(_,t2)->letterm=ft2inletres=terminaltermin(* We can prefill find_cache here, even if it won't save much work. *)Hash_MTBDD_BDD.replacefind_cache(res,BDD.one)@@TerminalSet.singletonterm;res|If(_,var,then_,else_)->mkvar(update_alladd_cachethen_f)(update_alladd_cacheelse_f)inHash_MTBDD_BDD.replaceadd_cache(mtbdd,BDD.one)res;res;;(* Note: update could be a base function, but it is not because
of the need to update the cache. XXX: Which we do not do for now.*)(* Note: add is just a special version of update. *)letupdatemtbddbddf=letadd_cache=(* TODO: Should we share add_cache across invocations of
add? Probably not for my current use cases. *)(* try Hash_Terminal.find add_cache t *)(* with Not_found -> *)letnew_cache=Hash_MTBDD_BDD.create17in(* Hash_Terminal.replace add_cache t new_cache; *)new_cacheinletrecupdatemtbddbdd=tryHash_MTBDD_BDD.findadd_cache(mtbdd,bdd)withNot_found->letres=matchbddwith|BDD.Zero->mtbdd|BDD.One->update_alladd_cachemtbddf|BDD.If(_,var2,then2,else2)->letres=(matchmtbddwith|Terminal(_,t2)->letterm=ft2in(* let terminal = f t2 in *)letterminal=terminalterminletsingleton=TerminalSet.singletonterminadd_singleadd_cachemtbddbddterminalsingleton|If(_,var1,then1,else1)->(matchVar.comparevar1var2with|0->mkvar1(updatethen1then2)(updateelse1else2)|xwhenx<0->mkvar1(updatethen1bdd)(updateelse1bdd)|_->mkvar2(updatemtbddthen2)(updatemtbddelse2)))in(* Hash_MTBDD_BDD.replace find_cache (res,bdd) singleton; *)resinHash_MTBDD_BDD.replaceadd_cache(mtbdd,bdd)res;res(* Pre-fill the cache in the likely case of a further call to find. *)(* Note: it is important not to fill the cache for the
BDD.Zero case; as find with a BDD of Zero should bottom,
and not the singleton. *)inupdatemtbddbdd;;endendend(* Test. *)moduleVar=structtypet=intletequal=(==)letcompare=Stdlib.comparelethashx=xletprettyfmtx=Format.fprintffmt"%d"xendincludeMake(Var);;(* Tests BDDs. *)openBDD(* include WithCache(struct let cache_default_size = 251 end);; *)letbdd1=var1;;letbdd2=var2;;letbdd3=bdd1&&~bdd2;;letbdd4=bdd2&&~bdd1;;assert(bdd3==bdd4);;letbdd5=bdd1==>~bdd2;;letbdd6=(!~bdd1)||~bdd2;;assert(bdd5==bdd6);;!~bdd6;;letbdd7=!~bdd1;;letbdd8=bdd1||~bdd7;;assert(bdd8==BDD.one);;(* Tests MTBDDs. *)moduleMTBDD=MTBDD_Make(structtypet=intlethash=Hashtbl.hashletequal=(==)letprettyfmtx=Format.fprintffmt"%d"xend);;openMTBDDletbdd1=terminal3;;letbdd2=terminal4;;letbdd3=mk1bdd1bdd2;;letbdd4=terminal5;;letbdd5=mk2bdd2bdd4;;map1(funx->x+1)bdd3;;map1(funx->8)bdd3;;map2(+)bdd3bdd5;;letbdd6=mk3bdd1bdd3;;letbdd7=mk4bdd3bdd4;;map2(+)bdd6bdd7;;letbdd8=mk5(terminal8)(terminal8);;assert(bdd8==(terminal8));;moduleIntSet=Set.Make(structletcompare=Stdlib.comparetypet=intend)moduleWS=With_Set(IntSet);;assert((WS.add(mk1(terminal3)(terminal4))(BDD.(!~)(BDD.var1))3)==(terminal3));;assert((WS.add(mk1(terminal3)(terminal4))(BDD.var1)4)==(terminal4));;