1 % (c) 2012-2024 Lehrstuhl fuer Softwaretechnik und Programmiersprachen,
2 % Heinrich Heine Universitaet Duesseldorf
3 % This software is licenced under EPL 1.0 (http://www.eclipse.org/org/documents/epl-v10.html)
4
5 :- module(b2sat, [init_satsolver/0, bt_sat/2,
6 solve_predicate_with_satsolver_free/2,
7 solve_predicate_with_satsolver_free/4,
8 solve_predicate_with_satsolver_in_state/4,
9 solve_predicate_with_satsolver/2]).
10
11 :- use_module('../../src/module_information.pl').
12 :- module_info(group,satsolver).
13 :- module_info(description,'This is the interface for ProB to use various SAT solvers (glucose, Z3)').
14
15 :- use_module(probsrc(tools),[start_ms_timer/1, stop_ms_timer_with_msg/2,
16 stop_ms_timer_with_debug_msg/2, get_elapsed_walltime/2]).
17 :- use_module(probsrc(debug),[debug_format/3]).
18 :- use_module(probsrc(error_manager),[add_error/3, add_internal_error/2]).
19 :- use_module(b_to_cnf).
20
21 :- use_module(library(lists), [maplist/2,maplist/3,exclude/3]).
22
23 :- use_module(satsolver). % Glucose API
24
25
26 :- use_module(probsrc(bsyntaxtree), [find_typed_identifier_uses/2, def_get_texpr_id/2]).
27 :- use_module(probsrc(solver_interface), [set_up_typed_localstate_for_pred/4]).
28 :- use_module(probsrc(b_compiler),[b_optimize/6]).
29
30 % call to solve a predicate with sat solver assuming all variables are free:
31 solve_predicate_with_satsolver_free(BFormula,Result) :-
32 solve_predicate_with_satsolver_free(BFormula,_,Result,[]).
33 solve_predicate_with_satsolver_free(BFormula,LocalState,Result,Options) :-
34 find_typed_identifier_uses(BFormula,UsedIdentifiers),
35 set_up_typed_localstate_for_pred(UsedIdentifiers,BFormula,TypedVals,LocalState),
36 b_optimize(BFormula,[],LocalState,[],NewTyped,no_wf_available),
37 solve_predicate_with_satsolver(NewTyped,TypedVals,LocalState,Result,Options).
38
39
40 % call to solve a predicate with sat solver with a provided state with variable/constant values:
41 solve_predicate_with_satsolver_in_state(BFormula,State,Result,Options) :-
42 find_typed_identifier_uses(BFormula,UsedIdentifiers),
43 exclude(is_already_declared_in_state(State),UsedIdentifiers,FilteredIdentifiers),
44 set_up_typed_localstate_for_pred(FilteredIdentifiers,BFormula,TypedVals,LocalState),
45 b_optimize(BFormula,[],LocalState,State,NewTyped,no_wf_available),
46 append(LocalState,State,State2),
47 solve_predicate_with_satsolver(NewTyped,TypedVals,State2,Result,Options).
48 is_already_declared_in_state(State,TID) :- def_get_texpr_id(TID,ID), memberchk(bind(ID,_),State).
49
50 solve_predicate_with_satsolver(BFormula,State) :-
51 solve_predicate_with_satsolver(BFormula,[],State,Result,[]),
52 Result = solution(_).
53
54 solve_predicate_with_satsolver(BFormula,TypedVals,State,Result,Options) :-
55 %write(' :sat '),nl, translate:nested_print_bexpr(BFormula),nl,
56 init_satsolver,!,
57 if(solve_pred2(BFormula,TypedVals,State,Result,Options),true,Result=error).
58 solve_predicate_with_satsolver(_,_,_,error,_).
59
60 :- use_module(probsrc(b_enumerate), [b_tighter_enumerate_all_values/2]).
61 :- use_module(probsrc(kernel_waitflags),
62 [init_wait_flags_with_call_stack/2,ground_wait_flags/1,
63 ground_det_wait_flag/1]).
64
65 solve_pred2(BFormula,TypedVals,State,Result,Options) :-
66 init_wait_flags_with_call_stack(WF,[prob_command_context(sat_solving,unknown)]),
67 if(solve_pred3(BFormula,State,Result,Options,WF),
68 (b_tighter_enumerate_all_values(TypedVals,WF),
69 ground_wait_flags(WF)),
70 Result=contradiction_found).
71
72 solve_pred3(BFormula,State,Result,Options,WF) :-
73 start_ms_timer(T1),
74 % Note: b_to_cnf will also look up all identifiers
75 (b_to_cnf_wf(BFormula,State,CNF,WF)
76 -> stop_ms_timer_with_profile(T1,b_to_cnf_walltime,'Converting B to CNF'),
77 ground_det_wait_flag(WF),
78 start_ms_timer(T2),
79 %portray_cnf(CNF),
80 %translate:print_bstate(State),nl,
81 solve_cnf(T2,CNF,State,Result,Options)
82 ; Result=error
83 ).
84
85 solve_cnf(T2,CNF,State,Result,Options) :-
86 bt_sat(CNF,Options),
87 stop_ms_timer_with_profile(T2,sat_walltime,'Formula is SATisfiable'),
88 Result = solution(State).
89 solve_cnf(T2,_,_,_Result,_Options) :-
90 stop_ms_timer_with_profile(T2,sat_walltime,'Formula is UNSATisfiable'),
91 fail. % fail to avoid pending co-routines of b_to_cnf
92
93 :- public portray_cnf/1.
94 portray_cnf(CNF) :- write('CNF:'),nl,maplist(portray_cl,CNF).
95 portray_cl(Clause) :- format(' ~w~n',[Clause]).
96
97
98 :- use_module(covsrc(hit_profiler),[add_to_profile_stats/2]).
99 stop_ms_timer_with_profile(Timer,Category,Msg) :-
100 get_elapsed_walltime(Timer,WT),
101 add_to_profile_stats(Category,WT),
102 stop_ms_timer_with_msg(Timer,Msg),
103 flush_output.
104
105
106 % --------------
107
108
109 % backtracking solving:
110 bt_sat(CNF,Options) :-
111 get_new_solver(SolverID,Options),
112 call_cleanup(bt_sat_aux(SolverID,CNF),
113 reset_solver(SolverID)).
114
115 bt_sat_aux(SolverID,CNF) :-
116 addCnf2Solver(SolverID,CNF,FVars,NrVars),!,
117 %toDimacs(SolverID,'satsolver_dimacs.cnf'), % comment in to export CNF to Dimacs format
118 debug_format(19,'Solving for ~w sat variables~n',[NrVars]),
119 flush_output,
120 solve_and_get_model_from_solver(SolverID,Model),
121 debug_format(19,'Model = ~w~n',[Model]),
122 bt_sat3(SolverID,FVars,Model).
123 bt_sat_aux(SolverID,_) :-
124 debug_format(19,'Inconsistency detected while adding clauses to SAT Solver ~w~n',[SolverID]),
125 fail.
126
127
128 bt_sat3(SolverID,FVars,Model) :-
129 debug_format(19,'Assigning Model~n',[]),
130 assign_model_from_solver(SolverID,FVars,Model).
131 bt_sat3(SolverID,FVars,Model) :-
132 debug_format(19,'Adding negated model to find another solution~n',[]),
133 add_negated_model(SolverID,Model),
134 solve_and_get_model_from_solver(SolverID,NewModel),
135 bt_sat3(SolverID,FVars,NewModel).
136
137 add_negated_model(SolverID,Model):-
138 maplist(neg,Model,NoAsgn),
139 add_extra_clause_to_solver(SolverID,NoAsgn).
140
141 neg(V,VN) :- VN is -V.
142
143 % -----------------
144
145 addCnf2Solver(SolverID,Cnf,FVars,NrVars):-
146 term_variables(Cnf,FVars),!,
147 length(FVars,NrVars), length(Cnf,ClNr),
148 format('Clauses: ~w, SAT Variables: ~w, Solver ID: ~w ~n',[ClNr,NrVars,SolverID]),
149 add_to_profile_stats(sat_clauses,ClNr),
150 add_to_profile_stats(sat_vars,NrVars),
151 \+ \+ (bind2index(FVars,3,_FN),
152 %portray_cnf(Cnf),
153 simplify_cnf(Cnf,SCnf),
154 AllCnf = [[1],[-2]|SCnf], % add true literal 1 and false literal 2
155 add_cnf_clauses_to_solver(SolverID,[1,2|FVars],AllCnf)
156 %,neg(FN,FNeg), add_cnf_clauses([[FN,FNeg]],SolverID) % why did we add this tautology ?
157 ).
158
159 % simplify CNF in case ProB propagation now has instantiated some of the SAT variables to pred_true/pred_false/...
160 simplify_cnf([],[]).
161 simplify_cnf([Clause|T],Res) :-
162 (simplify_clause(Clause,SC)
163 -> Res=[SC|TR]
164 ; Res=TR), % useless clause
165 simplify_cnf(T,TR).
166
167 simplify_clause([],[]).
168 simplify_clause([FALSE|T],Res) :- false_lit(FALSE),!, simplify_clause(T,Res).
169 simplify_clause([TRUE|_],_Res) :- true_lit(TRUE),!,fail. % clause useless, it is true
170 simplify_clause([Lit|T],[Lit|Res]) :- simplify_clause(T,Res).
171
172 true_lit(1).
173 true_lit(pred_true).
174 true_lit(neg(L)) :- false_lit(L).
175 false_lit(2).
176 false_lit(pred_false).
177 false_lit(neg(L)) :- true_lit(L).
178
179 % ------------------
180
181 % dispatching code: chooses between Glucose and Z3
182
183 :- use_module(smt_solvers_interface(smt_solvers_interface),[smt_add_cnf/3, smt_add_extra_clause_cnf/2,
184 smt_solve_cnf/2, smt_reset_cnf/1]).
185
186 get_new_solver(z3,Options) :- member(use_satsolver(z3),Options),!.
187 get_new_solver(SolverID,_) :- % use glucose
188 new_solver(SolverID).
189
190 reset_solver(z3) :- !, smt_reset_cnf(z3).
191 reset_solver(SolverID) :- delete_solver(SolverID).
192
193 add_cnf_clauses_to_solver(z3,FVars,AllCnf) :- !,
194 smt_add_cnf(z3,[1,2|FVars],AllCnf).
195 add_cnf_clauses_to_solver(SolverID,_,AllCnf) :-
196 add_cnf_clauses(AllCnf,SolverID).
197
198 add_extra_clause_to_solver(z3,MiniSatCl) :- !,
199 smt_add_extra_clause_cnf(z3,MiniSatCl).
200 add_extra_clause_to_solver(SolverID,MiniSatCl) :-
201 add_clause(SolverID,MiniSatCl).
202
203 solve_and_get_model_from_solver(z3,AVLModel) :- !,
204 smt_solve_cnf(z3,AVLModel).
205 solve_and_get_model_from_solver(SolverID,Model) :-
206 solve(SolverID),
207 debug_format(19,'Get Model~n',[]),
208 get_model(SolverID,[_|Model]). % model will be something like [-2,3,-4]
209
210
211 assign_model_from_solver(z3,FVars,Model) :- !,
212 debug:debug_println(19,assign_z3_model(FVars,Model)),
213 assign_z3_model(FVars,Model).
214 %assign_model_from_solver(_,FVars,Model) :- !,
215 % %write(fvars(FVars)),nl, observe_fvars(FVars),trace,
216 % assign_z3_model(FVars,[1|Model]). % this does the binding in Prolog, TODO: check performance wrt C call below
217 assign_model_from_solver(SolverID,FVars,_Model) :-
218 assign_model(SolverID,[1|FVars]).
219 % Note: assign model calls SP_unify one by one, and can fail when ProB triggers co-routines
220 % Co-routines are, however, only triggered once the full model has been assigned
221
222 % debugging code to observe instantiations:
223 observe_fvars(FVars) :- observe_fvars(FVars,1,FVars).
224 observe_fvars([],_,_).
225 observe_fvars([H|T],I,FVars) :- when(nonvar(H),format('Bound sat var nr. ~w to ~w : ~w~n',[I,H,FVars])), I1 is I+1,
226 observe_fvars(T,I1,FVars).
227
228 assign_z3_model(VarsList,[1,-2|Model]) :- !, % for z3
229 assign_z3_aux(VarsList,3,Model).
230 assign_z3_model(VarsList,[-2|Model]) :- !, % for glucose, minisat, ...
231 assign_z3_aux(VarsList,3,Model).
232 assign_z3_model(_,Model) :- add_internal_error('Z3 solution incorrect:',assign_z3_model(_,Model)), fail.
233
234 assign_z3_aux([],_,TM) :- !,
235 (TM=[] -> true ; add_internal_error('Too many literals in model:',assign_z3_aux([],_,TM))).
236 assign_z3_aux([H|T],Nr,[Model|TM]) :- !,
237 % format('Assigning ~w VAR to ~w~n',[Nr,Model]),
238 (Model=Nr -> H=pred_true
239 ; Model is -Nr -> H=pred_false
240 ; add_internal_error('Unexpected literal:',assign_z3_aux([H|T],Nr,[Model|TM])),
241 fail
242 ),
243 N1 is Nr+1,
244 assign_z3_aux(T,N1,TM).
245 assign_z3_aux(V,Nr,TM) :- add_internal_error('Model mismatch:',assign_z3_aux(V,Nr,TM)).
246
247 % ------------------
248
249
250 add_cnf_clauses([Cl|Cls],SolverID):-!,
251 to_minisat_cl(Cl,MiniSatCl),
252 %format('Adding clause to solver ~w: ~w~n',[SolverID,MiniSatCl]),
253 add_clause(SolverID,MiniSatCl), % add_clause can fail when SAT solver detects inconsistency directly
254 add_cnf_clauses(Cls,SolverID).
255 add_cnf_clauses([],_):-!.
256
257 to_minisat_cl([],[]).
258 to_minisat_cl([Lit1|TL],[ML1|TM]) :- to_minisat_lit(Lit1,ML1), to_minisat_cl(TL,TM).
259
260 % pred_true/pred_false should be eliminated in simplify_cnf above
261 to_minisat_lit(pred_true,1) :- !.
262 to_minisat_lit(pred_false,2) :- !.
263 to_minisat_lit(neg(NL),MSLit) :- !, to_minisat_neg_lit(NL,MSLit).
264 to_minisat_lit(X,R) :- integer(X),!, R=X. % a Prolog variable that has been bound by bind2index to a number
265 to_minisat_lit(X,R) :- add_internal_error('Illegal literal: ',to_minisat_lit(X,R)), R=X.
266
267 to_minisat_neg_lit(pred_false,1) :- !.
268 to_minisat_neg_lit(pred_true,2) :- !.
269 to_minisat_neg_lit(X,R) :- integer(X),!, R=neg(X). % a Prolog variable that has been bound by bind2index to a number
270 to_minisat_neg_lit(X,R) :- add_internal_error('Illegal literal: ',to_minisat_neg_lit(X,R)), R=neg(X).
271
272 % bind a list of variables to numbers (representing literals in .cnf)
273 bind2index([N|Ns],N,FN) :- N1 is N+1, bind2index(Ns,N1,FN).
274 bind2index([],N,FN):-!, FN is N - 1.