1 % (c) 2009-2023 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
6 :- module(unsat_cores,[unsat_core/2,
7 unsat_core_list_with_time_limit/5, unsat_chr_core_list_with_auto_time_limit/5,
8 unsat_core_wth_auto_time_limit/5,
9 unsat_core_with_fixed_conjuncts/3, unsat_chr_core_with_fixed_conjuncts_auto_time_limit/4,
10 minimum_unsat_core/2,
11 minimum_unsat_core_with_fixed_conjuncts/3,
12 tcltk_unsat_core_properties/3, unsat_core_properties_table/1,
13 quick_bup_core_up_to/4, quick_bup_core/4]).
14
15 :- use_module(library(terms),[term_size/2]).
16 :- use_module(library(between),[between/3]).
17 :- use_module(library(ordsets),[ord_intersect/2, ord_union/3]).
18 :- use_module(library(lists),[min_member/3, reverse/2, select/3, last/2, maplist/2, maplist/3, nth1/3]).
19
20 :- use_module(probsrc(bsyntaxtree)).
21 :- use_module(probsrc(solver_interface), [solve_predicate/5]).
22 :- use_module(probsrc(preferences), [get_preference/2]).
23 :- use_module(probsrc(debug), [debug_println/2, debug_print/1,debug_format/3, silent_mode/1, set_silent_mode/1, formatsilent/2]).
24 :- use_module(probsrc(self_check), [assert_must_succeed/1,assert_must_fail/1]).
25 :- use_module(probsrc(translate),[translate_bexpression_with_limit/3, set_unicode_mode/0, unset_unicode_mode/0]).
26 :- use_module(probsrc(specfile),[get_specification_description/2]).
27 :- use_module(probsrc(error_manager),[get_tk_table_position_info/2,add_warning/4, add_message/4,add_internal_error/2]).
28 :- use_module(probsrc(tools_strings),[ajoin/2]).
29 :- use_module(probsrc(preferences), [temporary_set_preference/3, reset_temporary_preference/2]).
30 :- use_module(probsrc(solver_interface), [solve_predicate/4]).
31 :- use_module(probsrc(tools),[split_list/4]).
32
33 :- use_module(cdclt_solver('cdclt_solver'), [cdclt_solve_predicate/3]).
34 :- use_module(smt_solvers_interface(smt_solvers_interface),[smt_solve_predicate/5]).
35
36 :- use_module(probsrc(module_information),[module_info/2]).
37 :- module_info(group,cbc).
38 :- module_info(description,'Computation of unsatisfiable cores of predicates.').
39
40 % possible options are:
41 % - keep_exact_result: keep exact top-level result obtained for entire predicate (e.g., to find cause of time-out)
42 % - no_warning_if_sol_found
43 % - ignore_if_sol_found: potentially dangerous and one has to check manually that GlobalRes is a contradiction
44 % - auto_time_out_factor(Factor)
45 % - fixed_conjuncts(FixedList)
46 % - branch_and_bound
47 % - inspect_nested_conjuncts(BOOL) (default true)
48 % - inspect_quantifiers(BOOL) (default true)
49
50
51 :- assert_must_succeed((unsat_core(b(falsity,pred,[]),Core), (ground(Core), Core = b(falsity,pred,_)))).
52 :- assert_must_succeed((unsat_core(b(conjunct(b(falsity,pred,[]),b(truth,pred,[])),pred,[]),Core), (ground(Core), Core = b(falsity,pred,_)))).
53
54 % for meta_interface and ProB2:
55 unsat_core_properties_table(list([list([PropsHeader,'Source'])|ResList])) :-
56 get_preference(translation_limit_for_table_commands,Limit),
57 get_specification_description(properties,PROPS),
58 unsat_core_of_properties(CorePredicates,[no_warning_if_sol_found],StrRes),
59 ajoin([PROPS,' : ',StrRes],PropsHeader),
60 set_unicode_mode,
61 call_cleanup(findall(list([TS,PosInfo]),
62 (member(CP,CorePredicates), translate_bexpression_with_limit(CP,Limit,TS),
63 get_tk_table_position_info(CP,PosInfo)),ResList),
64 unset_unicode_mode).
65
66 % HasUCLabels is true if the CorePredicates have UnsatCore special labels like @TO, @WD, ...
67 tcltk_unsat_core_properties(list(CorePredStrings),HasUCLabels,StrRes) :-
68 % TO DO: transmit runtime from Runtime to FINALISE SETUP_CONSTANTS to avoid re-checking Properties
69 unsat_core_of_properties(CorePredicates,[],StrRes),
70 set_unicode_mode,
71 call_cleanup(maplist(translate:translate_bexpression,CorePredicates,CorePredStrings),
72 unset_unicode_mode),
73 (member(CP,CorePredicates), pred_has_unsat_core_label(CP,_) -> HasUCLabels=true ; HasUCLabels=false),
74 maplist(add_warn,CorePredicates). % so that we see the spans of the unsat core in the edit window
75
76 unsat_core_of_properties(CorePredicates,Options,StrRes) :-
77 bmachine:b_get_properties_from_machine(Properties), % TO DO: also provide for unsat component
78 debug_print('Computing (efficient) unsat core for PROPERTIES'),nl,
79 unsat_chr_core_list_with_auto_time_limit(Properties,5000,Options,GlobalResult,CorePredicates),
80 (explain_result(GlobalResult,Explanation) -> StrRes=Explanation ; StrRes='**UNKNOWN**').
81
82 add_warn(Conjunct) :-
83 add_warning(unsat_cores,'This is part of an unsatisfiable core of the PROPERTIES clause: ',Conjunct,Conjunct).
84
85
86 % TODO: cleanup / rewrite predicate in order to minimize cores
87 % i.e. implications could be rewritten into conjuncts
88
89 :- assert_must_fail((minimum_unsat_core(b(conjunct(b(falsity,pred,[]),
90 b(conjunct(b(less(b(integer(1),integer,[]),b(identifier(x),integer,[])),pred,[]),
91 b(less(b(identifier(x),integer,[]),b(integer(1),integer,[])),pred,[])),pred,[])),pred,[]),Core),
92 (ground(Core), Core = b(less(b(integer(1),integer,[]),b(identifier(x),integer,[])),pred,_)))).
93 :- assert_must_fail((minimum_unsat_core(b(conjunct(b(falsity,pred,[]),
94 b(conjunct(b(less(b(integer(1),integer,[]),b(identifier(x),integer,[])),pred,[]),b(less(b(identifier(x),integer,[]),b(integer(1),integer,[])),pred,[])),pred,[])),pred,[]),Core),
95 (ground(Core), Core = b(less(b(identifier(x),integer,[]),b(integer(1),integer,[])),pred,_)))).
96 % there should be two cores
97 :- assert_must_succeed((findall(Core, unsat_core(b(conjunct(b(falsity,pred,[]),
98 b(conjunct(b(less(b(integer(1),integer,[]),b(identifier(x),integer,[])),pred,[]),b(less(b(identifier(x),integer,[]),b(integer(1),integer,[])),pred,[])),pred,[])),pred,[]),Core),
99 ListOfCores), length(ListOfCores,2))). % was 5
100
101 % call if you know how much time (in ms) it took to find inconsistency in Predicate
102 unsat_core_list_with_time_limit(Predicate,TimeToFindContradiction,Options,GlobalRes, CorePredicates) :-
103 temporary_set_timeout(TimeToFindContradiction,CHNG1),
104 % this time will be multiplied by 2 below
105 call_cleanup(unsat_core_list(Predicate,Options,GlobalRes,CorePredicates),
106 reset_temporary_preference(time_out,CHNG1)).
107
108
109 unsat_chr_core_list_with_auto_time_limit(Predicate,MaxTime,Options,GlobalRes,CorePredicates) :-
110 temporary_set_preference(use_chr_solver,true,CHNG1),
111 call_cleanup(unsat_core_wth_auto_time_limit(Predicate,MaxTime,Options,GlobalRes,CorePredicates),
112 reset_temporary_preference(use_chr_solver,CHNG1)).
113
114 % automatically determine time limit by checking that full predicate is false and registering time
115 unsat_core_wth_auto_time_limit(Predicate,MaxTime,Options,GlobalRes,CorePredicates) :-
116 % TODO: if unsat_core_target(.) provided and fixed time-out then we do not need to compute this:
117 get_time_out_for_comp_pred(Predicate,_Nr,CompPred,TimeToFindContradiction,Options,GlobalRes),
118 progress_info(GlobalRes),
119 (solution_found(GlobalRes)
120 -> ( member(no_warning_if_sol_found,Options)
121 -> add_message(unsat_cores,'Predicate has a solution, unsat core cannot be computed: ',Predicate,Predicate)
122 ; member(ignore_if_sol_found,Options)
123 -> true
124 ; add_warning(unsat_cores,'Predicate has a solution, unsat core cannot be computed: ',Predicate,Predicate)
125 ),
126 CorePredicates=[Predicate]
127 ; (member(auto_time_out_factor(Factor),Options) -> true ; Factor=120),
128 Limit0 is min(((TimeToFindContradiction*Factor)/100),MaxTime), %print(time_limit(Limit)),nl,
129 (member(min_time_out(MinTime),Options) -> true ; MinTime = 5),
130 Limit is max(MinTime,Limit0),
131 (member(unsat_core_target(Target),Options) -> true ; Target = GlobalRes),
132 % with unsat_core_target(contradiction_found) we can look for real contradictions even if the whole predicate has a time-out or translation error
133 debug_format(19,'Time out: ~w ms, target for unsat core: ~w~n',[Limit,Target]),
134 unsat_core_list_with_time_limit(CompPred,Limit,Options,Target,CorePredicates)
135 ).
136
137 %get_time_out_for_comp_pred(SubPred,1,SubPred,TimeToFindContradiction,Options,Res) :- !,get_time_out_for_pred(SubPred,TimeToFindContradiction,Options,Res).
138 get_time_out_for_comp_pred(Predicate,Nr,SubPred,TimeToFindContradiction,Options,Res) :- % partition
139 temporary_set_preference(partition_predicates_inline_equalities,false,OldValueOfPref), % avoid inlining equalities
140 % TO DO: maybe provide an option for this
141 call_cleanup(predicate_components(Predicate,Components),
142 reset_temporary_preference(partition_predicates_inline_equalities, OldValueOfPref)),
143 if( (nth1(Nr,Components,component(SubPred,Ids)), % select component via backtracking
144 debug_println(19,examining_component(Nr,Ids)),
145 get_time_out_for_pred(SubPred,TimeToFindContradiction,Options,Res),
146 progress_info(Res),
147 %print(sol_comp(Nr,Res)),nl,
148 % TODO: if translation_or_setup_failed and target is contradiction_found: filter predicate to that part that can be translated
149 \+ solution_found(Res)),
150 % we have found a sub-component that has a contradiction
151 true,
152 (SubPred=Predicate,
153 get_time_out_for_pred(SubPred,TimeToFindContradiction,Options,Res)
154 )
155 ).
156
157 get_time_out_for_pred(Predicate,TimeToFindContradiction,Options,Res) :-
158 silent_mode(CurMode), set_silent_mode(on),
159 statistics(walltime,[T1,_]),
160 solve_core_pred(Predicate, 2, Options, Res),
161 statistics(walltime,[T2,_]), TimeToFindContradiction is T2-T1,
162 set_silent_mode(CurMode), !.
163
164 temporary_set_timeout(TimeToFindContradiction,CHNG1) :-
165 number(TimeToFindContradiction), TimeToFindContradiction>=0,
166 NewTimeOut is round(TimeToFindContradiction+10), % in case we have 0; just add a few ms
167 temporary_set_preference(time_out,NewTimeOut,CHNG1).
168
169 unsat_core(Predicate,CorePredicate) :-
170 unsat_core(Predicate,no_solution_found,CorePredicate).
171 unsat_core(Predicate,GlobalResult,CorePredicate) :-
172 unsat_core_list(Predicate,[],GlobalResult,CorePreds),
173 conjunct_predicates(CorePreds,CorePredicate).
174
175 unsat_core_list(Predicate,Options,GlobalResult,CorePreds) :-
176 %print('COMPUTING CORE:'),nl,translate:nested_print_bexpr(Predicate),nl,
177 %(member(fixed_conjuncts(FL),Options) -> print('FIXED CONJ:'),nl,translate:nested_print_bexpr(FL),nl ; true),nl,
178 uc_conjunction_to_list(Predicate,Options,PredicateList),
179 length(PredicateList,OrigLen),
180 silent_mode(CurMode), set_silent_mode(on),
181 call_cleanup(unsat_core_with_options_lists(PredicateList,Options,GlobalResult,CorePreds),
182 set_silent_mode(CurMode)),
183 ( CurMode = on
184 -> true
185 ; length(CorePreds,NewLen),
186 debug_format(5, '~nFound core with ~w/~w conjuncts.~n', [NewLen,OrigLen])
187 ).
188
189 % strip outermost existential quantifiers so that we can examine the conjunction inside
190 uc_conjunction_to_list(b(exists(_V,Pred),pred,_), Options, List) :-
191 member(inspect_quantifiers(true), Options),
192 !,
193 conjunction_to_list(Pred, List).
194 % TO DO: add let_predicate (decomposed further below) ? forall ?
195 uc_conjunction_to_list(Pred, _, List) :- conjunction_to_list(Pred, List).
196
197 % called by ProB2
198 unsat_chr_core_with_fixed_conjuncts_auto_time_limit(Predicate,FixedConjuncts,MaxTimeLimit,CorePredicate) :-
199 temporary_set_preference(use_chr_solver,true,CHNG1), % TODO: put into options ?
200 Options = [],
201 call_cleanup(unsat_core_with_fixed_conjuncts_auto_time_limit(Predicate,FixedConjuncts,MaxTimeLimit,Options,CorePredicate),
202 reset_temporary_preference(use_chr_solver,CHNG1)).
203 unsat_core_with_fixed_conjuncts_auto_time_limit(Predicate,FixedConjuncts,MaxTimeLimit,Options,CorePredicate) :-
204 is_truth(FixedConjuncts),!,
205 Options=[],
206 unsat_core_wth_auto_time_limit(Predicate,MaxTimeLimit,Options,_,CorePreds),
207 conjunct_predicates(CorePreds,CorePredicate).
208 unsat_core_with_fixed_conjuncts_auto_time_limit(Predicate,FixedConjuncts,MaxTimeLimit,Options,CorePredicate) :-
209 conjunct_predicates([Predicate,FixedConjuncts],FullPred),
210 get_time_out_for_pred(FullPred,TimeToFindContradiction,Options,GlobalRes), % we cannot use component version due to FixedConjuncts
211 progress_info(GlobalRes),
212 (solution_found(GlobalRes)
213 -> add_warning(unsat_cores,'Predicate has a solution, unsat core cannot be computed: ',Predicate,Predicate),
214 CorePredicate=Predicate
215 ; Limit is min((TimeToFindContradiction*110/100),MaxTimeLimit),
216 temporary_set_timeout(Limit,CHNG1),
217 call_cleanup(unsat_core_with_fixed_conjuncts(Predicate,FixedConjuncts,GlobalRes,CorePredicate),
218 reset_temporary_preference(time_out,CHNG1))
219 ).
220
221 unsat_core_with_fixed_conjuncts(P,FC,Core) :- unsat_core_with_fixed_conjuncts(P,FC,no_solution_found,Core).
222 unsat_core_with_fixed_conjuncts(Predicate,FixedPreds,GlobalRes,CorePredicate) :-
223 conjunction_to_list(FixedPreds,FixedList),
224 Options = [fixed_conjuncts(FixedList)], % should we set inspect_nested_conjuncts(false) ?
225 unsat_core_list(Predicate,Options,GlobalRes,CorePreds),
226 conjunct_predicates(CorePreds,CorePredicate).
227
228 unsat_core_with_options_lists(PredicateList,Options,GlobalResult,CorePreds) :-
229 get_preference(unsat_core_algorithm,divide_and_conquer), !, % TO DO: pass GlobalResult
230 temporary_set_preference(allow_enumeration_of_infinite_types,false,OldValueOfPref),
231 (member(fixed_conjuncts(FixedList),Options) -> true ; FixedList=[]),
232 call_cleanup(unsat_core_fixed_conjuncts_lists_divide_and_conquer(PredicateList,FixedList,GlobalResult,Options,CorePreds),
233 reset_temporary_preference(allow_enumeration_of_infinite_types,OldValueOfPref)).
234 unsat_core_with_options_lists(PredicateList,Options,GlobalResult,CorePreds) :-
235 reset_max_core_size,
236 temporary_set_preference(allow_enumeration_of_infinite_types,true, OldValueOfPref),
237 %print('START: '),nl,translate:nested_print_bexpr(RPredicateList),nl,
238 call_cleanup(unsat_core_with_options_linear(PredicateList,Options, GlobalResult, CorePreds),
239 reset_temporary_preference(allow_enumeration_of_infinite_types, OldValueOfPref)),
240 (member(branch_and_bound,Options) -> length(CorePreds,Len), set_max_core_size(Len) ; true).
241
242
243 unsat_core_with_options_linear(PredicateList,Options, GlobalRes, CorePreds) :-
244 reverse(PredicateList, RPredicateList), % so that we properly treat WD we start removing from the back
245 compute_cores_linear(RPredicateList, Options, GlobalRes, CorePreds).
246
247 compute_cores_linear(RPredicateList, Options, GlobalRes, CorePreds) :-
248 compute_core_linear_loop(RPredicateList, 0, [], Options, GlobalRes, InCore,NotInCore,AllPreds),!,
249 ( CorePreds = InCore
250 ; % find more cores:
251 debug_print('R'),
252 restart_core_linear(InCore,NotInCore,AllPreds,Options,GlobalRes,CorePreds)
253 ).
254
255 % restart the unsat core algorithm with one conjunct from core removed from the original predicate list
256 restart_core_linear([C|Cs],Keep,AllPreds,Options,GlobalRes,CorePreds) :-
257 append(Keep,Cs,LL0), add_fixed_conj(Options,LL0,LL),
258 conjunct_predicates(LL, ConjunctionToCheck),
259 % print('TRY TO REMOVE CORE CONJUNCT FOR RESTART: '), translate:print_bexpr(C),nl,
260 solve_core_pred(ConjunctionToCheck, 1, Options, Res),
261 progress_info(Res),
262 must_keep_conjunct(Res,GlobalRes,Options),!,
263 % we need to keep C in PredicateList for restart; otherwise we change global result
264 restart_core_linear(Cs,[C|Keep],AllPreds,Options,GlobalRes,CorePreds).
265 restart_core_linear([C|_],_,AllPreds,Options,GlobalRes,CorePreds) :-
266 % we can remove C from the global list
267 (\+ get_preference(unsat_core_algorithm,divide_and_conquer) -> ! ; true), % linear_greedy
268 remove_conjunct(C,AllPreds,NewAllPreds), % remove C, keeping original order
269 debug_print('C'),
270 compute_cores_linear(NewAllPreds, Options, GlobalRes, CorePreds).
271 restart_core_linear([C|Cs],Keep,AllPreds,Options,GlobalRes,CorePreds) :-
272 % try to remove another conjunct; we may find the same core multiple times
273 restart_core_linear(Cs,[C|Keep],AllPreds,Options,GlobalRes,CorePreds).
274
275 add_fixed_conj(Options,P,Res) :- member(fixed_conjuncts(FixedList),Options),!,
276 (append(FixedList,P,FP) -> Res=FP
277 ; add_internal_error('Illegal fixed list: ',FixedList), Res=P).
278 add_fixed_conj(_,P,P).
279
280 remove_conjunct(C,RPredicateList,NewRPredicateList) :-
281 select(CC,RPredicateList,NewRPredicateList), % TO DO: remember position of C and use nth1
282 same_texpr(C,CC),!.
283 remove_conjunct(C,RPredicateList,New) :-
284 add_internal_error('Could not find conjunct from unsat core: ',remove_conjunct(C,RPredicateList,New)),fail.
285
286
287 % implies_optional_hyp(C,DefinitelyInCore,_Hyp)
288 % implied_by_core(C,DefinitelyInCore)
289 % implies_optional_hyp(C,DefinitelyInCore,_Hyp)
290
291 % simple unsat core computation: remove conjuncts till satisfiability is reached
292 compute_core_linear_loop([], _,DefinitelyInCore, _Options, _, DefinitelyInCore,[],[]).
293 compute_core_linear_loop([C|Cs], Counter,DefinitelyInCore, Options, GlobalRes, CorePreds,NotInCore,AllPreds) :-
294 % remove C and check for satisfiability
295 append(DefinitelyInCore,Cs,LL0), add_fixed_conj(Options,LL0,LL),
296 conjunct_predicates(LL, ConjunctionToCheck),
297 % print('TRY TO REMOVE: '), translate:print_bexpr(C),nl,
298 solve_core_pred(ConjunctionToCheck, 1, Options, Res),
299 debug_println(4, solve_predicate_result(Res)),
300 progress_info(Res),
301 must_keep_conjunct(Res,GlobalRes,Options), % a solution has been found or a time-out occured - C can not be removed;
302 % normally corresponds to solve_result: contradiction_found or no_solution_found(unfixed_deferred_sets)
303 !,
304 % print('KEEPING: '), translate:print_bexpr(C),nl,
305 % TO DO: when looking for minimal unsat core we could provide an upper-bound for the length
306 increase_core_counter(Counter,Options,IncCtr),
307 (can_be_further_decomposed(C, CA, CB),
308 (member(inspect_nested_conjuncts(INC),Options) -> INC=true ; true)
309 -> % print('% Split: '), translate:print_bexpr(CA),nl, print('% '),translate:print_bexpr(CB),nl,
310 compute_core_linear_loop([CA,CB|Cs], Counter,DefinitelyInCore, Options, GlobalRes, CorePreds,NotInCore,AllPreds)
311 ; add_uc_info(Res, C, IC), AllPreds = [C|TAP],
312 append(DefinitelyInCore,[IC],NewDefInCore), % TODO: use DCG
313 compute_core_linear_loop(Cs, IncCtr,NewDefInCore, Options, GlobalRes, CorePreds,NotInCore,TAP)
314 ).
315 compute_core_linear_loop([C|Cs], Ctr, DefinitelyInCore, Options, GlobalRes, CorePreds,NotInCore,[C|AllPreds]) :-
316 % format('CAN BE REMOVED (~w): ',[GlobalRes]), translate:print_bexpr(C),nl,
317 % C can be removed - we no longer introduce a choice point to not remove C; further cores are found by restarting
318 NotInCore = [C|RestNotInCore],
319 compute_core_linear_loop(Cs, Ctr, DefinitelyInCore, Options, GlobalRes, CorePreds,RestNotInCore,AllPreds).
320
321 :- dynamic max_core_size/1.
322 increase_core_counter(Cur,Options,_) :- max_core_size(Max), Cur >= Max,
323 member(branch_and_bound,Options),!,format('B~w',[Max]),fail.
324 increase_core_counter(Cur,_,C1) :- C1 is Cur+1.
325
326 reset_max_core_size :- retractall(max_core_size(_)).
327 set_max_core_size(Max) :- retractall(max_core_size(_)), assertz(max_core_size(Max)).
328
329 % check if an optional hypothesis in the current core is implied by the rest and the new hyp C
330 %implies_optional_hyp(C,CurrentCore,Hyp) :-
331 % \+ is_truth(CurrentCore),
332 % select(Hyp,CurrentCore,Rest), % DO THIS TEST also for C which must be kept
333 % bsyntaxtree:get_texpr_info(Hyp,IL), member(optionally_in_core,IL),
334 % % print(' --> '),translate:print_bexpr(Hyp), print(' <== '),translate:print_bexpr(Rest),nl,
335 % prove_sequent(animation,Hyp,[C|Rest]).
336 %
337 %:- use_module(wdsrc(well_def_analyser),[prove_sequent/3]).
338 %implied_by_core(C,CurrentCore) :- prove_sequent(animation,C,CurrentCore).
339
340
341 add_uc_info(SolverRes, C, IC2) :-
342 ( ( ground(SolverRes),
343 SolverRes = solution(_), Labels = [],
344 UCInfo = [description('definitely in unsat core (solution)')])
345 ; ( SolverRes == error, Labels = ['WD'],
346 UCInfo = [description('definitely in unsat core (kept to prevent WD error)')])
347 ; ( SolverRes == time_out, Labels = ['TO'], % pretty printed as @TO
348 UCInfo = [description('possibly in unsat core (kept to prevent timeout)')])
349 ; ( ground(SolverRes), Labels = ['VTO'],
350 SolverRes = no_solution_found(enumeration_warning(_,_,_,_,_)),
351 UCInfo = [description('possibly in unsat core (kept to prevent virtual timeout)')])
352 ; ( SolverRes == no_solution_found(unfixed_deferred_sets), Labels = ['DEFSETS'],
353 UCInfo = [description('definitely in unsat core (kept to fix deferred sets)')])
354 ; ( ground(SolverRes), Labels = ['UNKNOWN'],
355 SolverRes = no_solution_found(_),
356 UCInfo = [description('possibly in unsat core (kept to prevent unknown solver result)')])
357 ), !,
358 add_texpr_infos(C, UCInfo, IC1),
359 add_labels_to_texpr(IC1,Labels,IC2).
360
361 uc_label('WD').
362 uc_label('TO').
363 uc_label('VTO').
364 uc_label('DEFSETS').
365
366 pred_has_unsat_core_label(Pred,Label) :- get_texpr_label(Pred,Label), uc_label(Label).
367
368 solution_found(Res) :-
369 Res \= contradiction_found,
370 Res \= no_solution_found(_),
371 Res \= time_out,
372 Res \= error,
373 Res \= clpfd_overflow.
374
375 % check if a removed conjunct has to be kept in unsat core given solver_result (w/o conjunct) and global result
376 % possible value solution(_), contradiction_found, no_solution_found, ...
377 must_keep_conjunct(solution(_),_,_) :- !.
378 must_keep_conjunct(X,X,_) :- !,fail. % same result as global result, e.g., WD error or time_out
379 must_keep_conjunct(_,_,Options) :- member(keep_exact_result,Options),!.
380 must_keep_conjunct(contradiction_found,_,_) :- !,fail. %
381 must_keep_conjunct(no_solution_found(X),GlobalRes,_) :- !,
382 must_keep_no_sol_found(X,GlobalRes).
383 must_keep_conjunct(_,_,_).
384
385 must_keep_no_sol_found(enumeration_warning(_,_,_,_,critical),_).
386 % relevant for e.g. :core f={1|->1} & x:dom(f) & f(x)>1 with -p TRY_FIND_ABORT TRUE
387 must_keep_no_sol_found(solver_answered_unknown,_Global). % Global typically contradiction_found
388 % TODO: look if there are other reasons where we should keep the conjunct
389
390 can_be_further_decomposed(TE,A,B) :- can_be_further_decomposed(TE,positive,A,B).
391 can_be_further_decomposed(b(E,pred,Info),NegPos,A,B) :- can_be_further_decomposed_aux(E,Info,NegPos,A,B).
392 can_be_further_decomposed_aux(conjunct(A,B),_,positive,A,B).
393 can_be_further_decomposed_aux(disjunct(A,B),_,negative,A,B).
394 can_be_further_decomposed_aux(disjunct(A,B),_,positive,D1,D2) :-
395 get_preference(unsat_core_algorithm,linear_greedy_decompose),!, % can blow up number of cases
396 (can_be_further_decomposed(A,positive,A1,A2) % here we could try divide conjuncts in middle
397 -> disjunct_predicates([A1,B],D1),
398 disjunct_predicates([A2,B],D2)
399 ; can_be_further_decomposed(B,positive,B1,B2)
400 -> disjunct_predicates([A,B1],D1),
401 disjunct_predicates([A,B2],D2)
402 ).
403 % above rule works for :core ((x=2&y=3) or (x=1&z=3)) & x>2
404 % for :core ((x=2 & y=3) or (x=3 & y=4)) & x>3 it sort of works but keeps both x/y
405 can_be_further_decomposed_aux(implication(A,B),_,positive,I1,I2) :-
406 (can_be_further_decomposed(B,positive,B1,B2) % A=>B1&B2 <--> A=>B1 & A=>B2
407 -> create_implication(A,B1,I1),
408 create_implication(A,B2,I2)
409 ; can_be_further_decomposed(A,negative,A1,A2) % (A1 or A2) => B <--> A1 => B & A2 => B
410 -> create_implication(A1,B,I1),
411 create_implication(A2,B,I2)).
412 can_be_further_decomposed_aux(implication(A,B),_,negative,NA,B) :- % A=>B <--> not(A) or B
413 create_negation(A,NA).
414 can_be_further_decomposed_aux(negation(D),_,NegPos,NA,NB) :- neg_context(NegPos,PosNeg),
415 can_be_further_decomposed(D,PosNeg,A,B), % not(A &/or B) <--> not(A) or/& not(B)
416 create_negation(A,NA),
417 create_negation(B,NB).
418 can_be_further_decomposed_aux(let_predicate(V,E,P),Info,NegPos,LEA,LEB) :-
419 can_be_further_decomposed(P,NegPos,A,B),
420 LEA = b(let_predicate(V,E,A),pred,Info), % used_ids could be wrong; TO DO: think about exists
421 LEB = b(let_predicate(V,E,B),pred,Info).
422 can_be_further_decomposed_aux(lazy_let_pred(V,E,P),Info,NegPos,LEA,LEB) :-
423 can_be_further_decomposed(P,NegPos,A,B),
424 LEA = b(lazy_let_pred(V,E,A),pred,Info),
425 LEB = b(lazy_let_pred(V,E,B),pred,Info).
426
427 neg_context(positive,negative). neg_context(negative,positive).
428
429 % a binary search algorithm in order to only need log(n) instead of n solver calls
430 % Idea: split list of conjuncts in half, C = [A,B]
431 unsat_core_fixed_conjuncts_lists_divide_and_conquer([],_,_,_,_) :- !, fail. % we split too far!
432 unsat_core_fixed_conjuncts_lists_divide_and_conquer([C],_FixedConjuncts,_GlobalResult,_Options,[C]) :-
433 !. % if there is only one conjunct left, it has to belong to the core
434 unsat_core_fixed_conjuncts_lists_divide_and_conquer(Clauses,FixedConjuncts,GlobalResult,Options,Core) :-
435 split_in_half(Clauses,A,B),
436 % print('SPLIT: '), print(A),nl,print(B),nl,
437 unsat_core_fixed_conjuncts_lists_divide_and_conquer_split(A,B,FixedConjuncts,GlobalResult,Options,Core).
438
439 unsat_core_fixed_conjuncts_lists_divide_and_conquer_split(A,B,FixedConjuncts,GlobalResult,Options,Core) :-
440 append(A,FixedConjuncts,AllConjuncts),
441 conjunct_predicates(AllConjuncts,APred),
442 % print('SOLVING LHS: '), translate:print_bexpr(APred),nl,
443 solve_core_pred(APred, 1, Options, ARealRes),
444 progress_info(ARealRes),
445 % print(result(ARealRes)),nl,
446 !, % do not backtrack into solving or multiple results can be found leading to duplicated cores
447 unsat_core_fixed_conjuncts_lists_divide_and_conquer_recur1(ARealRes,A,B,FixedConjuncts,GlobalResult,Options,Core).
448
449 progress_info(ARes) :- progress_info_aux(ARes),!,flush_output(user_output).
450 progress_info_aux(time_out) :- progress_print('T').
451 progress_info_aux(error) :- progress_print('WD').
452 progress_info_aux(no_solution_found(E)) :- progress_info_no_sol_found_aux(E).
453 progress_info_aux(contradiction_found) :- progress_print('-').
454 progress_info_aux(translation_or_setup_failed) :- progress_print('FAILED'). % Z3 or cvc4 translation failed
455 progress_info_aux(solution(_)) :- progress_print('+').
456 progress_info_aux(clpfd_overflow) :- progress_print('O').
457 progress_info_aux(UNKNOWN) :- debug_format(5, '*~w*', [UNKNOWN]).
458
459 %progress_print(C) :- !, write(C).
460 progress_print(C) :- debug_print(C).
461
462 progress_info_no_sol_found_aux(unfixed_deferred_sets) :- progress_print('-').
463 progress_info_no_sol_found_aux(solver_answered_unknown) :- progress_print('U').
464 progress_info_no_sol_found_aux(enumeration_warning(_,_,_,_,_)) :- progress_print('U(ENUM)').
465 progress_info_no_sol_found_aux(UNKNOWN) :- debug_format(5, '*~w*', [UNKNOWN]).
466
467 explain_result(time_out,'UNKNOWN (TIME_OUT)').
468 explain_result(error,'UNKNOWN (ERROR)').
469 explain_result(clpfd_overflow,'UNKNOWN (CLPFD OVERFLOW)').
470 explain_result(no_solution_found(E),R) :- explain_no_sol(E,R).
471 explain_result(contradiction_found,'FALSE').
472 explain_result(solution(_),'TRUE').
473 explain_result(_,'UNKNOWN').
474
475 explain_no_sol(solver_answered_unknown, 'UNKNOWN').
476 explain_no_sol(unfixed_deferred_sets,'FALSE (DEFERRED SETS)').
477 explain_no_sol(enumeration_warning(_,_,_,_,_), 'UNKNOWN (ENUMERATION WARNING)').
478 explain_no_sol(_, 'UNKNOWN').
479
480 definite_prob_result(solution(_),_).
481 definite_prob_result(Res,Options) :- contradiction_result(Res,Options).
482
483 solve_core_pred(BPred,_TOFactor,Options,Res) :- % translate:print_bexpr(BPred),nl,
484 memberchk(try_prob_solver_first(TimeoutFactor),Options),
485 solve_predicate(BPred,_State,TimeoutFactor,Res),
486 formatsilent('ProB solve_predicate result : ~w~n',[Res]),
487 definite_prob_result(Res,Options), !.
488 solve_core_pred(BPred,TOFactor,Options,Res) :- % print(' --> Z3 SOLVE: '), translate:print_bexpr(BPred),nl,
489 member(use_smt_solver(Solver),Options),!,
490 Opts = [smt_time_out_factor(TOFactor), check_sat_skeleton(10), instantiate_quantifier_limit(5)],
491 %formatsilent('Starting SMT solver ~w with ~w~n',[Solver,Opts]),
492 smt_solve_predicate(Solver,Opts,BPred,_,Res),
493 formatsilent('~w solve_predicate result : ~w~n',[Solver,Res]).
494 solve_core_pred(BPred,_TOFactor,Options,Res) :-
495 member(use_cdclt_solver,Options),!, % TODO: pass TOFactor to cdclt
496 (cdclt_solve_predicate(BPred,_,Res) -> true
497 ; Res= contradiction_found,
498 add_internal_error('Solver call failed:',cdclt_solve_predicate(BPred,_,Res))
499 ).
500 % TODO: add wd-prover
501 solve_core_pred(BPred,TOFactor,Options,Res) :-
502 (member(use_smt_mode/SMT,Options) -> true ; SMT=true),
503 (member(use_clpfd_solver/CLPFD,Options) -> true ; CLPFD=true),
504 (member(use_chr_solver/CHR,Options) -> T=[use_chr_solver/CHR] ; T=[]),
505 solve_predicate(BPred,_BState,TOFactor,[use_smt_mode/SMT,use_clpfd_solver/CLPFD,ignore_wd_errors|T],Res).
506
507
508 unsat_core_fixed_conjuncts_lists_divide_and_conquer_recur1(ARealRes,A,_B,FixedConjuncts,GlobalResult,Options,Core) :-
509 \+ must_keep_conjunct(ARealRes,GlobalResult,[]), % A is (virtually / effectively) unsat. Recur on A.
510 unsat_core_fixed_conjuncts_lists_divide_and_conquer(A,FixedConjuncts,GlobalResult,Options,Core).
511 unsat_core_fixed_conjuncts_lists_divide_and_conquer_recur1(ARealRes,A,B,FixedConjuncts,GlobalResult,Options,Core) :-
512 append(B,FixedConjuncts,AllConjuncts),
513 conjunct_predicates(AllConjuncts,BPred),
514 % print('SOLVING RHS: '), translate:print_bexpr(BPred),nl,
515 solve_core_pred(BPred,1,Options,BRealRes),
516 progress_info(BRealRes),
517 % print(result(BRealRes)),nl,
518 !,
519 unsat_core_fixed_conjuncts_lists_divide_and_conquer_recur2(ARealRes,BRealRes,A,B,FixedConjuncts,GlobalResult,Options,Core).
520
521 unsat_core_fixed_conjuncts_lists_divide_and_conquer_recur2(_ARealRes,BRealRes,_A,B,FixedConjuncts,GlobalResult,Options,Core) :-
522 \+ must_keep_conjunct(BRealRes,GlobalResult,[]), % B is (virtually / effectively) unsat. Recur on B.
523 !,
524 unsat_core_fixed_conjuncts_lists_divide_and_conquer(B,FixedConjuncts,GlobalResult,Options,Core).
525 unsat_core_fixed_conjuncts_lists_divide_and_conquer_recur2(ARealRes,BRealRes,A,B,FixedConjuncts,GlobalResult,Options,Core) :-
526 % the interesting case: if both are sat
527 must_keep_conjunct(ARealRes,GlobalResult,[]),
528 must_keep_conjunct(BRealRes,GlobalResult,[]),
529 % minimize A assuming usat core includes B
530 append(B,FixedConjuncts,FixedAndB),
531 unsat_core_fixed_conjuncts_lists_divide_and_conquer(A,FixedAndB,GlobalResult,Options,ACore),
532 % now ACore & B is unsat and minimal but not minimum core
533 % minimize B assuming ACore
534 append(ACore,FixedConjuncts,FixedAndA),
535 unsat_core_fixed_conjuncts_lists_divide_and_conquer(B,FixedAndA,GlobalResult,Options,BCore),
536 append(ACore,BCore,Core).
537
538 %divide_and_conquer_recur_condition(contradiction_found).
539 %divide_and_conquer_recur_condition(no_solution_found(X)) :-
540 % % to do: if the outermost conjunct already generates a time_out or critical enumeration warning then this should be enabled as a recur condition
541 % X \= enumeration_warning(_,_,_,_,critical).
542 %%divide_and_conquer_recur_condition(time_out).
543
544 split_in_half(C, A, B) :-
545 length(C,L),
546 Half is L // 2,
547 length(A, Half),
548 append(A, B, C).
549
550 % there is currently no known good algorithm to compute the minimum unsat core
551 % we can just compute every core and take the minimum one out of the list
552 :- assert_must_succeed((minimum_unsat_core(b(conjunct(b(falsity,pred,[]),
553 b(conjunct(b(less(b(integer(1),integer,[]),b(identifier(x),integer,[])),pred,[]),b(less(b(identifier(x),integer,[]),b(integer(1),integer,[])),pred,[])),pred,[])),pred,[]),Core),
554 (ground(Core), Core = b(falsity,pred,_)))).
555 minimum_unsat_core(Predicate,CorePredicate) :-
556 minimum_unsat_core_with_fixed_conjuncts(Predicate,[],CorePredicate).
557
558 minimum_unsat_core_with_fixed_conjuncts(Predicate,FixedConjuncts,CorePredicate) :-
559 Options = [branch_and_bound,fixed_conjuncts(FixedConjuncts)],
560 findall(CoreList,
561 unsat_core_list(Predicate,Options,no_solution_found,CoreList),
562 Cores),
563 reset_max_core_size,
564 min_member(compare_length,MinCs,Cores),
565 conjunct_predicates(MinCs,CorePredicate).
566
567 compare_length(P1,P2) :- length(P1,L1), length(P2,L2),
568 (L1<L2 -> true
569 ; L1=L2 ->
570 term_size(P1, T1),
571 term_size(P2, T2),
572 T1 < T2
573 ).
574
575 % ----------------------------------
576
577 % quick_bup_core
578
579 % finding small unsat cores bottom-up by looking for 1, 2, ... conjuncts and looking for contradiction_found
580 % useful e.g. for Z3 where some conjuncts are complex and lead to time outs or translation failed errors
581 % could also be used with WD prover instead or in addition to Z3
582 % currently cannot be used to find unsat core for timeout or unknown result
583 % useful as a "canary" for PROPERTIES/axioms as a quick check or as additional quick check
584
585
586 preprocess_conjuncts([],_,_,[]).
587 preprocess_conjuncts([Conj|T],Nr,Options,ListResult) :-
588 format(' ==> Pre-processing conjunct ~w and checking for individual inconsistency~n',[Nr]),
589 %translate:print_bexpr(Conj),nl,
590 % we could =remove try_prob_solver_first(_) from Options, if we want to filter non-translatable conjuncts!
591 solve_core_pred(Conj, 0.02, Options, Res),
592 (explain_result(Res,Expl) -> debug_format(19,'Result for conjunct ~w: ~w (~w)~n',[Nr,Expl,Res])),
593 N1 is Nr+1,
594 (should_remove_conjunct(Res)
595 -> % remove this conjunct; it can only disturb finding a core for a contradiction
596 preprocess_conjuncts(T,N1,Options,ListResult)
597 ; find_identifier_uses(Conj,[],UsedIds),
598 ListResult = [conj(Nr,Conj,Res,UsedIds)|PT],
599 (contradiction_result(Res,Options)
600 -> PT=[] % stop processing, we have found an unsat core of a single conjunct
601 ; preprocess_conjuncts(T,N1,Options,PT))
602 ).
603
604 should_remove_conjunct(no_solution_found(translation_failed)).
605
606 contradiction_result(contradiction_found,_).
607 contradiction_result(no_solution_found(unfixed_deferred_sets),Options) :-
608 member(allow_unfixed_deferred_sets,Options).
609
610 quick_bup_core_up_to(Typed,MaxSize,CoreList,Result) :-
611 quick_bup_core(Typed,[use_smt_solver(z3axm),
612 maximum_core_size(MaxSize),
613 try_prob_solver_first(fixed_time_out(5))],
614 CoreList,Result).
615
616 quick_bup_core(Predicate,Options,CoreList,Result) :-
617 uc_conjunction_to_list(Predicate,Options,List),
618 preprocess_conjuncts(List,1,Options,ProcessedList),
619 (last(ProcessedList,conj(_,Last,LR,_)),
620 contradiction_result(LR,Options)
621 -> CoreList = [Last], Result=LR
622 ; (member(maximum_core_size(MaxN),Options) -> true ; MaxN=3),
623 between(2,MaxN,N),
624 format(' ==> Finding cores of length ~w~n',[N]),
625 contradiction_check(N,ProcessedList,Options,CoreList,Result) -> true
626 ).
627
628
629
630 select_core_target(N,List,Res) :-
631 append(_,[conj(Nr,ConjN,_,UsedIdsN)|Rest],List), % now only use conjuncts to the right of ConjN
632 format(' ==> finding (~w-ary) conflicts with conjunct ~w: ',[N,Nr]),translate:print_bexpr(ConjN),nl,
633 N1 is N-1,
634 select_core_target(N1,Rest,[ConjN],UsedIdsN,Res).
635
636 has_common_ids(UsedIdsSoFar,conj(_,_,_,UsedIdsN)) :- ord_intersect(UsedIdsSoFar,UsedIdsN).
637
638 % select a core target list of predicates of length N
639 % TODO: this can still generate the same conjunct multiple times??
640 select_core_target(0,_,Core,_,Res) :- !, Res=Core.
641 select_core_target(N,List,CoreSoFar,UsedIdsSoFar,Res) :- %print(n(N,UsedIdsSoFar)),nl,
642 split_list(has_common_ids(UsedIdsSoFar),List,CommonList,DisjList),
643 append(_,[conj(_Nr,ConjN,_,UsedIdsN)|Rest],CommonList), % only choose conjuncts with common ids larger than Nr
644 %print(selected_conj(_Nr)),nl, translate:print_bexpr(ConjN),nl,
645 append(Rest,DisjList,RestList),
646 ord_union(UsedIdsN,UsedIdsSoFar,New),
647 N1 is N-1,
648 select_core_target(N1,RestList,[ConjN|CoreSoFar],New,Res).
649
650 contradiction_check(N,ProcessedList,Options,CoreList,Res) :-
651 select_core_target(N,ProcessedList,CoreList),
652 conjunct_predicates(CoreList,Core),
653 %nl,translate:print_bexpr(Core),nl,nl,
654 solve_core_pred(Core, 0.02, Options, Res),
655 progress_info(Res),
656 contradiction_result(Res,Options).
657
658 % could use WD prover as additional task:
659 % >>> :prove x:NATURAL --> NATURAL => not(-1:ran(x))
660 % Universally quantified predicate is PROVED with [prob-wd-prover(no_double_check)]
661
662 % example where we need three conjuncts: :z3-axm-qcore x<y & y<10 & x<10 & y+n<x & n=0