123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856(* Copyright (C) 2013, Thomas Leonard
* See the README file for details, or visit http://0install.net.
*)(** A general purpose SAT solver. *)(** The design of this solver is very heavily based on the one described in
the MiniSat paper "An Extensible SAT-solver [extended version 1.2]"
http://minisat.se/Papers.html
The main differences are:
- We care about which solution we find (not just "satisfiable" or "not").
- We take care to be deterministic (always select the same versions given
the same input). We do not do random restarts, etc.
- We add an at_most_one_clause (the paper suggests this in the Excercises, and
it's very useful for our purposes).
*)letdebug=falsemoduletypeUSER=sigtypetvalpp:Format.formatter->t->unitendmoduleVarID:sigtypettypemintvalcompare:t->t->intvalmake_mint:unit->mintvalissue:mint->tend=structtypet=inttypemint=intrefletcompare(a:int)(b:int)=compareabletmake_mint()=ref0letissuemint=leti=!mintinincrmint;iendmoduleMake(User:USER)=structtypevar_value=True|False|Undecidedletlog_debugfmt=letdo_printmsg=print_endline("sat: "^msg)inFormat.kasprintfdo_printfmttypesign=Pos|Negtypeclause=<(* [lit] is now [True]. Add any new deductions.
@return false if there is a conflict. *)propagate:lit->bool;(* Why are we causing a conflict?
@return a list of literals which caused the problem by all being True. *)calc_reason:litlist;(* Which literals caused [lit] to have its current value?
@return a list of literals which caused the problem by all being True. *)calc_reason_for:lit->litlist;(* For debugging *)pp:Format.formatter->unit;>(** The reason why a literal is set. *)andreason=|Clauseofclause(* the clause that caused this literal to be true *)|Externalofstring(* set externally (input fact or decider choice) *)andvar={id:VarID.t;(* A unique ID, used to test identity *)mutablevalue:var_value;(* True/False/Undecided *)mutablereason:reasonoption;(* The constraint that implied our value, if True or False *)mutablelevel:int;(* The decision level at which we got a value (when not Undecided) *)mutableundo:(lit->unit)list;(* Functions to call if we become unbound (by backtracking) *)watch_queue:clauseQueue.t;(* Clauses to notify when var becomes True *)neg_watch_queue:clauseQueue.t;(* Clauses to notify when var becomes False *)obj:User.t;(* The object this corresponds to (for our caller and for debugging) *)}andlit=(sign*var)letlit_equal(s1,v1)(s2,v2)=s1==s2&&v1==v2moduleVarSet=Set.Make(structtypet=varletcompareab=VarID.comparea.idb.idend)moduleLitSet=Set.Make(structtypet=litletcompare(s1,v1)(s2,v2)=matchVarID.comparev1.idv2.idwith|0->compares1s2|x->xend)typesolution=lit->boolletmake_varidobj={id;value=Undecided;reason=None;level=-1;undo=[];watch_queue=Queue.create();neg_watch_queue=Queue.create();obj;}typet={id_maker:VarID.mint;(* Propagation *)mutablevars:varlist;propQ:litQueue.t;(* propagation queue *)(* Assignments *)mutabletrail:litlist;(* order of assignments, most recent first *)mutabletrail_lim:intlist;(* decision levels (len(trail) at each decision) *)mutabletoplevel_conflict:bool;mutableset_to_false:bool;(* we are finishing up by setting everything else to False *)}letpp_reasonf=function|Clauseclause->clause#ppf|Externalmsg->Format.pp_print_stringfmsgletneg=function|(Pos,var)->(Neg,var)|(Neg,var)->(Pos,var)letvar_of_lit(_,var)=varletwatch_queue=function|(Pos,var)->var.watch_queue|(Neg,var)->var.neg_watch_queueletremove_duplicateslits=letseen=refLitSet.emptyinletrecfind_unique=function|[]->[]|(x::xs)whenLitSet.memx!seen->find_uniquexs|(x::xs)->seen:=LitSet.addx!seen;x::find_uniquexsinfind_uniquelitsletswaparrij=let(first,second)=(arr.(i),arr.(j))inarr.(i)<-second;arr.(j)<-firstletstring_of_value=function|True->"true"|False->"false"|Undecided->"undecided"exceptionConflictingClauseofclauseexceptionSolveDoneofsolutionoptiontypeadded_result=|AddedFactofbool(* Result of enqueing the new fact *)|AddedClauseofclauseletcreate()=ifdebugthenlog_debug"--- new SAT problem ---";{id_maker=VarID.make_mint();vars=[];propQ=Queue.create();trail=[];trail_lim=[];toplevel_conflict=false;set_to_false=false;}(* For nicer log_debug messages *)letname_litf(sign,var)=matchsignwith|Pos->User.ppfvar.obj|Neg->Format.fprintff"not(%a)"User.ppvar.objletpp_comma_sepf()=Format.pp_print_stringf", "letpp_litsflits=Format.fprintff"[%a]"(Format.pp_print_list~pp_sep:pp_comma_sepname_lit)litsletlit_value(sign,var)=matchsignwith|Pos->var.value|Neg->matchvar.valuewith|Undecided->Undecided|True->False|False->Trueletget_user_data_for_litlit=(var_of_litlit).objletpp_lit_assignmentfl=letinfo=var_of_litlinFormat.fprintff"%a=%s"User.ppinfo.obj(string_of_valueinfo.value)letpp_lit_reasonflit=match(var_of_litlit).reasonwith|None->Format.pp_print_stringf"no reason (BUG)"|Some(Externalreason)->Format.pp_print_stringfreason|Some(Clausec)->letreason=c#calc_reason_forlitinletpp_sepf()=Format.pp_print_stringf" && "inFormat.pp_print_list~pp_seppp_lit_assignmentfreason(* Why is [lit] assigned the way it is? For debugging. *)letexplain_reasonlit=letvalue=lit_valuelitinifvalue=Undecidedthen"undecided!"else(Format.asprintf"%a => %a"pp_lit_reasonlitpp_lit_assignmentlit)letget_decision_levelproblem=List.lengthproblem.trail_limletadd_variableproblemobj:lit=(* if debug then log_debug "add_variable('%s')" obj; *)leti=VarID.issueproblem.id_makerinletvar=make_variobjinproblem.vars<-var::problem.vars;(Pos,var)(* [lit] is now [True].
[reason] is the clause that is asserting this.
@return [false] if this immediately causes a conflict. *)letenqueueproblemlitreason=ifdebugthenlog_debug"enqueue: %a (%a)"name_litlitpp_reasonreason;letold_value=lit_valuelitinmatchold_valuewith|False->false(* Conflict *)|True->true(* Already set (shouldn't happen) *)|Undecided->letvar_info=var_of_litlitinvar_info.value<-iffstlit==NegthenFalseelseTrue;var_info.level<-get_decision_levelproblem;var_info.reason<-Somereason;problem.trail<-lit::problem.trail;Queue.addlitproblem.propQ;true(* Pop most recent assignment from [trail] *)letundo_oneproblem=matchproblem.trailwith|[]->assertfalse|lit::rest->(* if debug then log_debug "(pop %s)" (name_lit lit); *)letvar_info=var_of_litlitinvar_info.value<-Undecided;var_info.reason<-None;var_info.level<--1;problem.trail<-rest;whilevar_info.undo<>[]doletcb=List.hdvar_info.undoinvar_info.undo<-List.tlvar_info.undo;cblitdoneletcancelproblem=letn_this_level=List.lengthproblem.trail-List.hdproblem.trail_liminifdebugthenlog_debug"backtracking from level %d (%d assignments)"(get_decision_levelproblem)n_this_level;for_i=1ton_this_leveldoundo_oneproblem;done;problem.trail_lim<-List.tlproblem.trail_limletcancel_untilproblemlevel=whileget_decision_levelproblem>leveldocancelproblemdone(** Process the propQ.
Returns None when done, or the clause that caused a conflict. *)letpropagateproblem=(* if debug then log_debug "propagate: queue length = %d" (Queue.length problem.propQ); *)trywhilenot(Queue.is_emptyproblem.propQ)doletlit=Queue.takeproblem.propQinletold_watches=Queue.create()inletwatches=watch_queuelitinQueue.transferwatchesold_watches;(* if debug then log_debug "%s -> True : watches: %d" (name_lit lit) (Queue.length old_watches); *)(* Notifiy all watchers *)whilenot(Queue.is_emptyold_watches)doletclause=Queue.takeold_watchesinifnot(clause#propagatelit)then((* Conflict *)(* Re-add remaining watches *)Queue.transferold_watcheswatches;(* No point processing the rest of the queue as
we'll have to backtrack now. *)Queue.clearproblem.propQ;raise(ConflictingClauseclause))donedone;NonewithConflictingClausec->Somecletimpossibleproblem()=problem.toplevel_conflict<-true(* Call [clause#propagate lit] when lit becomes True *)letwatch_litlitclause=(* if debug then log_debug "%s is watching for %s to become True" clause#to_string (name_lit lit); *)Queue.addclause(watch_queuelit)letunion_clauseproblemlits=object(self:clause)(* Try to infer new facts.
We can do this only when all of our literals are False except one,
which is undecided. That is,
False... or X or False... = True => X = True
To get notified when this happens, we tell the solver to
watch two of our undecided literals. Watching two undecided
literals is sufficient. When one changes we check the state
again. If we still have two or more undecided then we switch
to watching them, otherwise we propagate.
Returns false on conflict. *)methodpropagatelit=(* [neg lit] has just become False *)(*if debug then log_debug("%s: noticed %s has become False" % (self, self.solver.name_lit(neg(lit)))) *)(* For simplicity, only handle the case where self.lits[1]
is the one that just got set to False, so that:
- value[lits[0]] = Undecided | True
- value[lits[1]] = False
If it's the other way around, just swap them before we start. *)iflit_equallits.(0)(neglit)thenswaplits01;iflit_valuelits.(0)=Truethen((* We're already satisfied. Do nothing. *)watch_litlit(self:>clause);true)else(assert(lit_valuelits.(1)=False);(* Find a new literal to watch now that lits[1] is resolved, *)(* swap it with lits[1], and start watching it. *)letrecfind_not_falsei=ifi=Array.lengthlitsthen((* Only lits[0], is now undefined, so set it to True. *)watch_litlit(self:>clause);enqueueproblemlits.(0)(Clause(self:>clause)))else(matchlit_valuelits.(i)with|Undecided|True->(* If it's True then we've already done our job,
so this means we don't get notified unless we backtrack, which is fine. *)swaplits1i;watch_lit(neglits.(1))(self:>clause);true|False->find_not_false(i+1))infind_not_false2)(* We can only cause a conflict if all our lits are False, so they're all the cause.
e.g. if we are "A or B or not(C)" then "not(A) and not(B) and C" causes a conflict. *)methodcalc_reason=List.mapneg(Array.to_listlits)(** Which literals caused [lit] to have its current value? *)methodcalc_reason_forlit=assert(lit_equallitlits.(0));(* The cause is everything except lit. *)letrecget_causei=ifi=Array.lengthlitsthen[]else(letl=lits.(i)iniflit_equalllitthenget_cause(i+1)elsenegl::get_cause(i+1))inget_cause0methodppf=letpp_sepf()=Format.pp_print_stringf", "inFormat.fprintff"<some: %a>"(Format.pp_print_list~pp_sepname_lit)(Array.to_listlits)endexceptionConflict(* If one literal in the list becomes True, all the others must be False.
Preferred literals should be listed first. *)classat_most_one_clauseproblemlits=(* The single literal from our set that is True.
We store this explicitly because the decider needs to know quickly. *)letcurrent=refNoneinobject(self)methodpropagatelit=(* Re-add ourselves to the watch list.
(we we won't get any more notifications unless we backtrack,
in which case we'd need to get back on the list anyway) *)watch_litlit(self:>clause);(* value[lit] has just become true *)assert(lit_valuelit=True);(* if debug then log_debug("%s: noticed %s has become True" % (self, self.solver.name_lit(lit))) *)(* If we already propagated successfully when the first
one was set then we set all the others to false and
anyone trying to set one true will get rejected. And
if we didn't propagate yet, current will still be
None, even if we now have a conflict (which we'll
detect below). *)assert(!current=None);current:=Somelit;(* If we later backtrack, unset current *)letundolit=ifdebugthenlog_debug"(backtracking: no longer selected %a)"name_litlit;beginmatch!currentwith|Somel->assert(lit_equallitl)|None->assertfalseend;current:=Noneinletvar_info=var_of_litlitinvar_info.undo<-undo::var_info.undo;try(* We set all other literals to False. *)lits|>List.iter(funl->matchlit_valuelwith|Truewhennot(lit_equalllit)->(* Due to queuing, we might get called with current = None
and two versions already selected. *)ifdebugthenlog_debug"CONFLICT: already selected %a"name_litl;raiseConflict|Undecided->(* Since one of our lits is already true, all unknown ones
can be set to False. *)ifnot(enqueueproblem(negl)(Clause(self:>clause)))then(ifdebugthenlog_debug"CONFLICT: enqueue failed for %a"name_lit(negl);raiseConflict(* Can't happen, since we already checked we're Undecided *))|_->());truewithConflict->false(** If we caused a conflict, it's because two of our literals became true. *)methodcalc_reason=(* Find two True literals *)letrecfind_twofound=function|[]->assertfalse(* Don't know why! *)|(x::xs)whenlit_valuex<>True->find_twofoundxs|(x::xs)->matchfoundwith|None->find_two(Somex)xs|Somefirst->[first;x]infind_twoNonelits(** Which literals caused [lit] to have its current value? *)methodcalc_reason_forlit=(* Find the True literal. Any true literal other than [lit] would do. *)[List.find(funl->not(lit_equalllit)&&lit_valuel=True)lits]methodbest_undecided=(* if debug then log_debug "best_undecided: %s" (string_of_lits lits); *)List.find_opt(funl->lit_valuel=Undecided)litsmethodget_selected=!currentmethodppf=Format.fprintff"<at most one: %a>"pp_litslitsendletget_best_undecidedclause=clause#best_undecidedletget_selectedclause=clause#get_selected(* Returns the new clause if one was added, [AddedFact true] if none was added
because this clause is trivially True, or [AddedFact false] if the clause
caused a conflict. *)letinternal_at_most_oneproblemlits~learnt~reason=matchlitswith|[]->assertfalse|[lit]->(* A clause with only a single literal is represented
as an assignment rather than as a clause. *)AddedFact(enqueueproblemlitreason)|lits->letlits=Array.of_listlitsinletclause=union_clauseproblemlitsiniflearntthen((* lits[0] is Undecided because we just backtracked.
Start watching the next literal that we will backtrack over. *)letbest_level=ref(-1)inletbest_i=ref1infori=0to(Array.lengthlits)-1doletlit=lits.(i)inletlevel=(var_of_litlit).leveliniflevel>!best_levelthen(best_level:=level;best_i:=i)done;swaplits1!best_i;);(* Watch the first two literals in the clause (both must be
undefined at this point). *)letwatchi=watch_lit(neglits.(i))clauseinwatch0;watch1;AddedClauseclause(** Public interface. Only used before the solve starts. *)letat_least_oneproblem?(reason="input fact")lits=ifList.lengthlits=0then(problem.toplevel_conflict<-true;)else((* if debug then log_debug "at_least_one(%s)" (string_of_lits lits); *)ifList.exists(funl->(lit_valuel)=True)litsthen((* Trivially true already if any literal is True. *)())else(letseen=refLitSet.emptyinletrecsimplifyunique=function|[]->Someunique|(x::_)whenLitSet.mem(negx)!seen->None(* X or not(X) is always True *)|(x::xs)whenLitSet.memx!seen->simplifyuniquexs(* Skip duplicates *)|(x::xs)whenlit_valuex=False->simplifyuniquexs(* Skip values known to be False *)|(x::xs)->seen:=LitSet.addx!seen;simplify(x::unique)xsin(* At this point, [unique] contains only [Undefined] literals. *)matchsimplify[]litswith|None->()|Some[]->problem.toplevel_conflict<-true(* Everything in the list was False *)|Someunique->ifinternal_at_most_oneproblemunique~learnt:false~reason:(Externalreason)=AddedFactfalsethenproblem.toplevel_conflict<-true;))letimpliesproblem?reasonfirstrest=at_least_oneproblem?reason((negfirst)::rest)letat_most_oneproblemlits=assert(List.lengthlits>0);(* if debug then log_debug "at_most_one(%s)" (string_of_lits lits); *)(* If we have zero or one literals then we're trivially true
and not really needed for the solve. However, Zero Install
monitors these objects to find out what was selected, so
keep even trivial ones around for that.
*)(*if len(lits) < 2: *)(* return True # Trivially true *)(* Ensure no duplicates *)ifList.length(remove_duplicateslits)<>List.lengthlitsthen(invalid_arg(Format.asprintf"at_most_one(%a): duplicates in list!"pp_litslits));(* Ignore any literals already known to be False.
If any are True then they're enqueued and we'll process them
soon. *)letlits=List.filter(funl->lit_valuel<>False)litsinletclause=newat_most_one_clauseproblemlitsinList.iter(funl->watch_litl(clause:>clause))lits;clauseletanalyseproblem(original_cause:clause)=(* After trying some assignments, we've discovered a conflict.
e.g.
- we selected A then B then C
- from A, B, C we got X, Y
- we have a rule (original_cause): not(A) or not(X) or not(Y)
The simplest thing to do would be:
1. add the rule "not(A) or not(B) or not(C)"
2. unassign C
Then we'd deduce not(C) and we could try something else.
However, that would be inefficient. We want to learn a more
general rule that will help us with the rest of the problem.
We take the clause that caused the conflict (original_cause)
and ask it for its cause. In this case:
A and X and Y => conflict
Since X and Y followed logically from A, B, C there's no
point learning this rule; we need to know to avoid A, B, C
*before* choosing C. We ask the two variables deduced at the
current level (X and Y) what caused them, and work backwards.
e.g.
X: A and C => X
Y: C => Y
Combining these, we get the cause of the conflict in terms of
things we knew before the current decision level:
A and X and Y => conflict
A and (A and C) and (C) => conflict
A and C => conflict
We can then learn (record) the more general rule:
not(A) or not(C)
Then, in future, whenever A is selected we can remove C and
everything that depends on it from consideration.
*)letlearnt=ref[]in(* The general rule we're learning *)letbtlevel=ref0in(* The deepest decision in learnt *)letseen=refVarSet.emptyin(* The variables involved in the conflict *)letcounter=ref0in(* The number of pending variables to check *)(* [outcome] was caused by the literals [p_reason] all being True. Follow the
causes back, adding anything decided before this level to [learnt]. When
we get bored, return the literal we were processing at the time. *)letrecfollow_causesp_reasonoutcome=ifdebugthen(letpp_sepf()=Format.pp_print_stringf" and "inlog_debug"because %a => %s"(Format.pp_print_list~pp_sepname_lit)p_reasonoutcome);(* p_reason is in the form (A and B and ...) *)(* Check each of the variables in p_reason that we haven't
already considered:
- if the variable was assigned at the current level,
mark it for expansion
- otherwise, add it to learnt *)p_reason|>List.iter(funlit->letvar=var_of_litlitinifnot(VarSet.memvar!seen)then(seen:=VarSet.addvar!seen;letvar_info=var_of_litlitinifvar_info.level=get_decision_levelproblemthen((* We deduced this var since the last decision.
It must be in [trail], so we'll get to it
soon. Remember not to stop until we've processed it. *)(* if debug then log_debug "(will look at %s soon)" (name_lit lit); *)incrcounter)elseifvar_info.level>0then((* We won't expand lit, just remember it.
(we could expand it if it's not a decision, but apparently not doing so is useful) *)(* if debug then log_debug "Can't follow %s past a decision point" (name_lit lit); *)learnt:=neglit::!learnt;btlevel:=max!btlevel(var_info.level))(* else if debug then log_debug "Input fact: %s" (name_lit lit) *))(* else we already considered the cause of this assignment *));(* At this point, counter is the number of assigned
variables in [trail] at the current decision level that
we've seen. That is, the number left to process. Pop
the next one off [trail] (as well as any unrelated
variables before it; everything up to the previous
decision has to go anyway). *)(* On the first time round the loop, we must find the
conflict depends on at least one assignment at the
current level. Otherwise, simply setting the decision
variable caused a clause to conflict, in which case
the clause should have asserted not(decision-variable)
before we ever made the decision.
On later times round the loop, [counter] was already >
0 before we started iterating over [p_reason]. *)assert(!counter>0);(* Pop assignments until we find one we care about. *)letrecnext_interesting()=letlit=List.hdproblem.trailinletvar=var_of_litlitinletreason=var.reasoninundo_oneproblem;ifnot(VarSet.memvar!seen)then((* if debug then log_debug "(irrelevant: %s)" (name_lit lit); *)next_interesting();)else((reason,lit))inlet(reason,p)=next_interesting()in(* [reason] is the reason why [p] is True (i.e. it enqueued it). *)(* [p] is the literal we want to expand now. *)decrcounter;if!counter>0then(letcause=matchreasonwith|Some(Clausec)->c|Some(Externalmsg)->failwithmsg(* Can't happen *)|None->failwith"No reason!"in(* Can't happen *)letp_reason=cause#calc_reason_forpinletoutcome=Format.asprintf"%a"name_litpinifdebugthenlog_debug"why did %t lead to %s?"cause#ppoutcome;follow_causesp_reasonoutcome)else((* If counter = 0 then we still have one more
literal (p) at the current level that we
could expand. However, apparently it's best
to leave this unprocessed (says the minisat
paper). *)p)in(* Start with all the literals involved in the conflict. *)ifdebugthenlog_debug"why did %t lead to conflict?"original_cause#pp;letp=follow_causesoriginal_cause#calc_reason"conflict"inassert(!counter=0);(* p is the literal we decided to stop processing on. It's either
a derived variable at the current level, or the decision that
led to this level. Since we're not going to expand it, add it
directly to the learnt clause. *)letlearnt=negp::!learntinifdebugthen(letpp_sepf()=Format.pp_print_stringf" or "inlog_debug"learnt: %a"(Format.pp_print_list~pp_sepname_lit)learnt);(learnt,!btlevel)letget_assignmentvar=matchlit_valuevarwith|True->true|False->false|Undecided->assertfalseletrun_solverproblemdecide=(* Check whether we detected a trivial problem during setup. *)ifproblem.toplevel_conflictthen(ifdebugthenlog_debug"FAIL: toplevel_conflict before starting solve!";None)else(trywhiletruedo(* Use logical deduction to simplify the clauses
and assign literals where there is only one possibility. *)matchpropagateproblemwith|None->((* No conflicts *)(* if debug then log_debug "new state: %s" problem.assigns *)(* Pick a variable and try assigning it one way.
If it leads to a conflict, we'll backtrack and
try it the other way. *)letundecided=tryList.find(funinfo->info.value=Undecided)problem.varswithNot_found->(* Everything is assigned without conflicts *)(* if debug then log_debug "SUCCESS!"; *)raise(SolveDone(Someget_assignment))inletlit=ifproblem.set_to_falsethen((* Printf.printf "%s -> false\n" (name_lit undecided); *)(Neg,undecided))else(matchdecide()with|Somelit->lit|None->(* Switch to set_to_false mode (until we backtrack). *)problem.set_to_false<-true;letundo_=problem.set_to_false<-falseinundecided.undo<-undo::undecided.undo;(* Printf.printf "%s -> false\n" (name_lit undecided); *)(Neg,undecided))inifdebugthenlog_debug"TRYING: %a"name_litlit;letold=lit_valuelitinifold<>Undecidedthenfailwith(Format.asprintf"Decider chose already-decided variable: %a was %s"name_litlit(string_of_valueold));problem.trail_lim<-List.lengthproblem.trail::problem.trail_lim;letr=enqueueproblemlit(External"considering")inassertr)|Someconflicting_clause->ifget_decision_levelproblem=0then(ifdebugthenlog_debug"FAIL: conflict found at top level";raise(SolveDoneNone))else((* Figure out the root cause of this failure. *)let(learnt,backtrack_level)=analyseproblemconflicting_clausein(* We have learnt that something in [learnt] must be True or we get a conflict. *)cancel_untilproblembacktrack_level;matchinternal_at_most_oneproblemlearnt~learnt:true~reason:(Clauseconflicting_clause)with|AddedFacttrue->()|AddedFactfalse->(* This is what the Python would do. Perhaps it can't happen? *)lete=enqueueproblem(List.hdlearnt)(External"conflict!")inasserte;|AddedClausec->(* Everything except the first literal in learnt is known to
be False, so the first must be True. *)lete=enqueueproblem(List.hdlearnt)(Clausec)inasserte;)done;failwith"not reached"withSolveDonex->x)end