1 % (c) 2013-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 :- module(disprover, [disprove/5, disprove_with_opts/6, get_disprover_default_opts/1,
6 print_prover_result_stats/0]).
7
8 :- use_module(probsrc(module_information)).
9 :- module_info(group,cbc).
10 :- module_info(description,'Rodin Prover / Disprover').
11
12 % provide access to the Disprover algorithm with all hypotheses, selected hypotheses and goal
13
14 :- use_module(extension('user_signal/user_signal'), [user_interruptable_call_det/2]).
15
16 :- use_module(probsrc(solver_interface), [solve_predicate/5,type_check_in_machine_context/2]).
17 :- use_module(probsrc(preferences), [temporary_set_preference/3, reset_temporary_preference/2,
18 get_preference/2]).
19 :- use_module(probsrc(bsyntaxtree), [conjunct_predicates/2, create_negation/2,
20 find_typed_identifier_uses/3,
21 find_typed_identifier_uses_l/3,
22 get_texpr_types/2]).
23 :- use_module(probsrc(error_manager), [reset_errors/0, add_message/3,
24 add_warning/3, add_internal_error/2, add_error_and_fail/2]).
25 :- use_module(library(lists)).
26 :- use_module(probsrc(tools),[start_ms_timer/1, stop_ms_timer/1,stop_ms_timer_with_debug_msg/2,
27 get_elapsed_timer/2, combiner_timer/3]).
28 :- use_module(probsrc(debug), [debug_println/2,debug_mode/1, debug_format/3]).
29 :- use_module(probsrc(b_global_sets),[contains_unfixed_deferred_set/1]).
30 :- use_module(probsrc(external_functions),[observe_state/1]).
31 :- use_module(extrasrc(unsat_cores),[unsat_core/2]).
32 :- use_module(probsrc(pathes_lib), [unavailable_extension/2]).
33
34 disprove(Goal,AllHypotheses,SelectedHypotheses,Timeout,OutResult) :-
35 get_disprover_default_opts(Opts),
36 % Note: disprover_mode is always set to true
37 disprove_with_opts(Goal,AllHypotheses,SelectedHypotheses,Timeout,Opts,OutResult).
38
39 get_disprover_default_opts(Opts) :-
40 (unavailable_extension(chr_extension,_Reason)
41 -> CHR=false, format('Disabling CHR in disprover as extension not available~n',[]) ; CHR=true),
42 % default options:
43 Opts = [use_smt_mode/true,use_clpfd_solver/true,use_chr_solver/CHR].
44
45 % ----------
46
47 disprove_with_opts(Goal,AllHypotheses,SelectedHypotheses,Timeout,Opts,OutResult) :-
48 check_options(Opts),
49 temporary_set_preference(disprover_mode,true,DC),
50 temporary_set_preference(time_out,Timeout,TC),
51 start_ms_timer(Timer),
52 user_interruptable_call_det(disprove1(Goal,AllHypotheses,SelectedHypotheses,Opts,Result),InterruptResult),
53 %write(ir(InterruptResult)), nl,
54 (InterruptResult = interrupted -> OutResult = interrupted ; OutResult = Result),
55 %catch_internal_timeout(InterruptResult,Result,OutResult),
56 (debug_mode(on) -> write(disprove_result(OutResult)),nl,stop_ms_timer(Timer),nl ; true),
57 reset_temporary_preference(disprover_mode,DC),
58 reset_temporary_preference(time_out,TC).
59
60 check_options([]) :- !.
61 check_options([O|T]) :- !, (valid_option(O) -> true ; add_warning(disprover,'Illegal disprover option: ',O)),
62 check_options(T).
63 check_options(O) :- add_internal_error('Illegal disprover options: ',check_options(O)).
64
65 :- use_module(probsrc(solver_interface), [valid_solver_option/1]).
66 % Valid options
67 valid_option(disprover_option(X)) :- !,valid_disprover_option(X).
68 valid_option(X) :- valid_solver_option(X).
69
70 valid_disprover_option(observe_state).
71 valid_disprover_option(unsat_core).
72 valid_disprover_option(unsat_core_as_machine).
73 valid_disprover_option(export_po_as_machine(_)).
74 valid_disprover_option(setlog_prover_timeout(_)).
75 valid_disprover_option(wd_prover_timeout(_)).
76 valid_disprover_option(po_label(_)).
77
78 % ----------
79
80 :- use_module(probsrc(error_manager),[add_all_perrors/3]).
81 disprove1(Goal,AllHypotheses,SelectedHypotheses,Opts,Result) :-
82 catch(
83 (disprove2(Goal,AllHypotheses,SelectedHypotheses,Opts,Result) -> true
84 ; add_internal_error('Disprover failed on goal:',Goal),
85 fail),
86 type_errors(E),
87 (add_all_perrors(E,[],disprover), Result=type_error)).
88
89 %catch_internal_timeout(ok,contradiction_found,Out) :-
90 % error_manager:check_warning_occured(warning(_X),Y),
91 % is_time_out_message(Y), % Sebastian: why do we catch this ?? This is not necessarily a problem ??
92 % !, % Might be a problem for proofs, invalidate only if Result = contradiction_found
93 % Out = time_out. % However, this might be old code that is superseded by the enum warnings
94 %catch_internal_timeout(ok,Result,Result).
95
96 % is_time_out_message(Y) :- atom(Y), atom_codes(Y,Codes),
97 % append("TIME-OUT occurred while ProB was expanding", _, Codes).
98
99 % ProverContext: just a name for statistics
100 solve_disprover_predicate(ProverContext,Predicate,Opts,CBCResult) :-
101 TimeoutFactor = 1,
102 exclude(disprover_option,Opts,SOpts),
103 (member(disprover_option(observe_state),Opts) -> observe_state(State) ; true),
104 start_ms_timer(Timer),
105 (solve_predicate(Predicate,State,TimeoutFactor,SOpts,CBCResult)
106 -> register_prover_result(ProverContext,CBCResult,Timer)
107 ; register_prover_result(ProverContext,failed,Timer),
108 fail
109 ).
110 disprover_option(disprover_option(_)).
111
112
113 :- use_module(wdsrc(well_def_analyser),[prove_sequent/4]).
114 :- use_module(probsrc(tools_meta),[safe_time_out/3]).
115 :- use_module(extrasrc(b2setlog),[prove_using_setlog/5]).
116 prove_using_alternative_prover(setlog_prover,Goal,Hypotheses,Opts,TimeOutMs,Res) :-
117 start_ms_timer(Timer),
118 (prove_using_setlog(Goal,Hypotheses,Opts,TimeOutMs,Res)
119 -> (Res = contradiction_found
120 -> register_prover_result(setlog_prover,contradiction_found,Timer)
121 ; register_prover_result(setlog_prover,Res,Timer),
122 fail
123 )
124 ; register_prover_result(setlog_prover,failed,Timer),
125 fail
126 ).
127 prove_using_alternative_prover(wd_prover,Goal,Hypotheses,Opts,TimeOutMs,contradiction_found) :-
128 % quite often 0 or 1 ms are enough for TimeOutMs
129 (member(disprover_option(po_label(Label)),Opts) -> WDOpts=[po_label(Label)] ; WDOpts=[]),
130 debug_format(19,'Starting WD Prover (timeout ~w ms) for \'~w\' ~n',[TimeOutMs,Label]),
131 start_ms_timer(Timer),
132 (safe_time_out(prove_sequent(proving,Goal,Hypotheses,WDOpts),TimeOutMs,TimeOutRes)
133 -> (TimeOutRes=time_out
134 -> format('Timeout in WD Prover (~w ms)~n',[TimeOutMs]),
135 register_prover_result(wd_prover,time_out,Timer), fail
136 ; register_prover_result(wd_prover,contradiction_found,Timer))
137 ; register_prover_result(wd_prover,failed,Timer),
138 fail
139 ).
140
141
142 :- dynamic prover_stats/4.
143
144 % register the result of a prover for statistics / user feedback
145 register_prover_result(Prover,Result,Timer) :-
146 functor(Result,RF,_),
147 (retract(prover_stats(Prover,RF,PrevNr,PrevTime)) -> true ; PrevNr=0, PrevTime=0),
148 NewNr is PrevNr+1,
149 get_elapsed_timer(Timer,DeltaTimer),
150 combiner_timer(PrevTime,DeltaTimer,NewTimer),
151 assert(prover_stats(Prover,RF,NewNr,NewTimer)),
152 stop_ms_timer_with_debug_msg(Timer,prover_result(Prover,RF)).
153
154 print_prover_result_stats :-
155 \+ \+ prover_stats(_,_,_,_), % at least one call made
156 format('* Disprover statistics about individual prover calls and results:~n',[]),
157 format('~w,~w,~w,~w,~w,~w~n',['Prover','Result','Calls','Runtime','TotRuntime','Walltime']),
158 prover_stats(Prover,Result,Nr,timer(Runtime,TotRuntime,Walltime)),
159 format('~w,~w,~w,~w,~w,~w~n',[Prover,Result,Nr,Runtime,TotRuntime,Walltime]),
160 fail.
161 print_prover_result_stats.
162
163 :- public disprove2/5. % it is called by user_interruptable_call_det above; we should also add a meta_predicate annotation
164 :- use_module(probsrc(bsyntaxtree), [predicate_identifiers/2]).
165 disprove2(Goal,AllHypotheses,SelectedHypotheses,Opts,ResultOut) :-
166 (type_check_in_machine_context([Goal|AllHypotheses],[TGoal|TAllHypotheses]) -> true
167 ; add_error_and_fail(disprover,'Disprover typechecking goal failed')),
168 (type_check_in_machine_context(SelectedHypotheses,TSelectedHypotheses) -> true
169 ; add_error_and_fail(disprover,'Disprover typechecking selected hyps failed')),
170 disprove3(TGoal,TAllHypotheses,TSelectedHypotheses,Opts,ResultOut).
171
172
173 disprove3(TGoal,TAllHypotheses,TSelectedHypotheses,Opts,ResultOut) :-
174 % build predicate H1 & H2 .... & not Goal
175 create_negation(TGoal,NegatedGoal),
176 %(debug_mode(on) -> write(NegatedGoal), nl ; true),
177 (debug_mode(on) -> nl,write('Negated Goal:'), nl,
178 translate:nested_print_bexpr_as_classicalb(NegatedGoal),
179 write_hypothesis(TAllHypotheses)
180 ; true),
181 conjunct_predicates([NegatedGoal|TAllHypotheses],Predicate),
182 %tools:print_bt_message(solving),
183 ProofInfos = [disprover_result(final,all_and_selected,ResultOut),options(Opts)|PI1],
184 ( get_alternate_prover_timeout(Opts,Prover,Timeout),
185 Timeout>0,
186 prove_using_alternative_prover(Prover,TGoal,TAllHypotheses,Opts,Timeout,ResultWithAll)
187 % adds 5 extra PO proven in test 1800, and 4 more in test 1447
188 -> % DISCHARGED by WD Prover
189 (debug_mode(on) -> add_message(disprover,'Discharged by alternate prover: ',Prover:Timeout) ; true),
190 PI1 = [disprover_result(Prover,all,ResultWithAll)|PI2]
191 ; solve_disprover_predicate(prob_all_hyps,Predicate,Opts,ResultWithAll),
192 PI1 = [disprover_result(prob,all,ResultWithAll)|PI2]
193 ),
194 %tools:print_bt_message(solver_result(CBCResult)),
195 debug_println(10,disprover_result_with_all_hypotheses(ResultWithAll)),
196 disprove_double_check(ResultWithAll,NegatedGoal,TAllHypotheses,TSelectedHypotheses,Predicate,Opts,ResultOut,PI2),
197 (member(disprover_option(export_po_as_machine(File)),Opts)
198 -> export_po_as_machine(File,TAllHypotheses,TSelectedHypotheses,TGoal,ProofInfos)
199 ; true).
200
201 get_alternate_prover_timeout(Opts,Prover,Timeout) :-
202 member(disprover_option(setlog_prover_timeout(TO)),Opts),!,
203 (Prover=wd_prover,member(disprover_option(wd_prover_timeout(Timeout)),Opts) % try WD prover first
204 ;
205 Prover=setlog_prover, TO=Timeout).
206 get_alternate_prover_timeout(Opts,wd_prover,Timeout) :-
207 member(disprover_option(wd_prover_timeout(Timeout)),Opts),!.
208 get_alternate_prover_timeout(_,wd_prover,WDTimeout) :-
209 get_preference(time_out,GlobalTO),
210 WDTimeout is 20 + GlobalTO // 100.
211
212 % res <— solve( All Hyp & not G )
213 % if res = solution then
214 % return CounterExample Found, Goal definitely not provable
215 % else if unfixed deferred set used in selected Hypotheses or Goal then
216 % return Unknown // WE COULD TRY and find counter example here in case there was a time-out?
217 % else if res = fail (without time-out & without enumeration warning & no unfixed deferred set in All Hypotheses) then
218 % return Proof Found, Goal proven
219 % else
220 % res <— solve (Selected Hyp & not G) /* maybe use smaller time-out here ?? */
221 % if res = fail (without time-out & without enumeration warning) then
222 % return Proof Found, Goal proven
223 % else if res = solution then
224 % return CounterExample for selected Hypotheses found, Goal not provable from selected Hypotheses (but may be provable with all Hyp)
225 % else
226 % return Unknown
227 % fi
228 % fi
229
230 % we found a solution / counter example to all hyps no need to double check here.
231 % just clear up the resulting state, keeping only variables occuring in the goal.
232 % although: sometimes it is useful to have other IDs as well, at least in the same component as the Goal !
233 disprove_double_check(solution(X),NegatedGoal,_Hypotheses,_SelectedHypotheses,_,_,ResultOut,ProofInfos) :-
234 predicate_identifiers(NegatedGoal,GoalIdentifiers),
235 findall(binding(Id,Value,PP), get_binding_from_solution(X,GoalIdentifiers,0,Id,Value,PP), FilteredState),
236 ResultOut = solution(FilteredState),
237 ProofInfos = ['Counter example to all hypotheses found'].
238 % check if the unfixed deferred sets occur in selected hyps or goal
239 disprove_double_check(no_solution_found(unfixed_deferred_sets),NegatedGoal,_Hypotheses,SelectedHypotheses,_,_,
240 no_solution_found(unfixed_deferred_sets),ProofInfos) :-
241 find_typed_identifier_uses(NegatedGoal,[],GoalIdentifiers),
242 get_texpr_types(GoalIdentifiers,GoalTypes),
243 find_typed_identifier_uses_l(SelectedHypotheses,[],HypIdentifiers),
244 get_texpr_types(HypIdentifiers,HypTypes),
245 append(GoalTypes,HypTypes,Types),
246 % exclude all types that are not unfixed
247 include(contains_unfixed_deferred_set,Types,UnfixedTypes),
248 % see if an unfixed type is left
249 UnfixedTypes = [_|_],
250 !,
251 ProofInfos = ['Goal or selected hypotheses contain unfixed deferred sets', unfixed(UnfixedTypes)],
252 !.
253 % contradiction was found, i.e. there was neither a timeout nor an enumeration warning
254 disprove_double_check(contradiction_found,NegatedGoal,Hypotheses,SelectedHypotheses,FullPredicate,Opts,
255 FinalResult,ProofInfos) :-
256 ProofInfos = ['PO is proven using all hypotheses, no timeout or enumeration warning occured'],
257 %add_hit(po_discharged,prob_all),
258 (get_preference(double_evaluation_when_analysing,true)
259 -> check_negated(NegatedGoal,Hypotheses,SelectedHypotheses,FullPredicate,Opts,FinalResult)
260 ; FinalResult = contradiction_found).
261 disprove_double_check(Result,_NegatedGoal,Hypotheses,SelectedHypotheses,_,_Opts,ResultOut,ProofInfos) :-
262 same_conjunction_list(Hypotheses,SelectedHypotheses),!,
263 ProofInfos = ['Selected hypotheses identical to all hypotheses'],
264 Result=ResultOut.
265 % see what solving only selected hypotheses & not G gives us
266 disprove_double_check(_OldResult,NegatedGoal,Hypotheses,SelectedHypotheses,_,Opts,ResultOut,ProofInfos) :-
267 % No proof found with all hypotheses, now try selected ones only
268 % reset existing enumeration warnings,
269 % that might have been generated in a previous run
270 reset_errors,
271 (debug_mode(on)
272 -> nl,write('Trying to use only selected hypotheses.\nNegated Goal:'), nl,
273 translate:nested_print_bexpr_as_classicalb(NegatedGoal),
274 write_hypothesis(SelectedHypotheses)
275 ; true),
276 conjunct_predicates([NegatedGoal|SelectedHypotheses],Predicate),
277 %external_functions:observe_state(State),
278 solve_disprover_predicate(prob_selected_hyps,Predicate,Opts,Result),
279 ProofInfos = [disprover_result(prob,selected,Result)],
280 !,
281 disprove_triple_check(Result,NegatedGoal,Hypotheses,SelectedHypotheses,Predicate,Opts,ResultOut).
282
283
284 % we still have a contradiction -> double check it if preference set
285 disprove_triple_check(contradiction_found,NegatedGoal,Hypotheses,SelectedHypotheses,FullPredicate,Opts,FinalResult) :-
286 %add_hit(po_discharged,prob_selected),
287 (get_preference(double_evaluation_when_analysing,true) % preference set by Rodin Disprover
288 -> check_negated(NegatedGoal,Hypotheses,SelectedHypotheses,FullPredicate,Opts,FinalResult)
289 ; FinalResult = contradiction_found,
290 compute_unsat_core_if_requested(FullPredicate,Opts)
291 ).
292 disprove_triple_check(solution(X),NegatedGoal,_,_,_FullPredicate,_,ResultOut) :-
293 % Counter-Example to Goal for selected Hypotheses found
294 predicate_identifiers(NegatedGoal,GoalIdentifiers),
295 findall(binding(Id,Value,PP), get_binding_from_solution(X,GoalIdentifiers,10,Id,Value,PP), FilteredState),
296 ResultOut = solution_on_selected_hypotheses(FilteredState).
297 disprove_triple_check(time_out,_,_,_,_,_,time_out). % TODO: We could try Kodkod here!
298 disprove_triple_check(no_solution_found(X),_,_,_,_,_,no_solution_found(X)). % Or here?
299
300 :- use_module(library(ordsets),[ord_member/2]).
301 % get bindings from solution; showing all identifiers from goal and a few others as indicated by MaxIndexNonGoal
302 get_binding_from_solution(Solution,GoalIdentifiers,MaxIndexNonGoal,Id,Value,PP) :- MaxIndexNonGoal>=0,
303 sort(GoalIdentifiers,SG),
304 nth1(Nr,Solution,binding(Id,Value,PP)),
305 (Nr =< MaxIndexNonGoal -> true ; ord_member(Id,SG)).
306 % TO DO: rather than limiting this way, we could analyse the variables which are in the same component
307 % either by calling predicate_components or by storing component info in
308
309 check_negated(NegatedGoal,Hypotheses,_,FullOrigPredicate,Opts,ResultOut) :-
310 create_negation(NegatedGoal,Goal),
311 conjunct_predicates([Goal|Hypotheses],Predicate),
312 %tools:print_bt_message(check_negated),
313 debug_println(20,'Check whether we can also prove negation of goal'),
314 solve_disprover_predicate(prob_check_negated_goal,Predicate,Opts,Result),
315 %% tools:print_bt_message(solver_double_check_result(Result)),
316 !,
317 (Result = contradiction_found % proven both (H & not G) and (H & G)
318 -> ResultOut = contradiction_in_hypotheses,
319 translate:translate_bexpression_with_limit(NegatedGoal,GS),
320 add_warning(disprover_inconsistent_hypotheses,
321 'Disprover double check result: Contradiction in hypotheses (or Bug) detected for goal: ',
322 GS), %% check_negated(NegatedGoal,Hypotheses))
323 % translate:nested_print_bexpr_as_classicalb(Hypotheses),nl,nl,
324 compute_unsat_core_if_requested(Predicate,Opts)
325 % ; Result=solution(Bindings), format('Solution for negated goal:~n',[]), member(B,Bindings), print_binding(B),fail
326 ; ResultOut = contradiction_found,
327 compute_unsat_core_if_requested(FullOrigPredicate,Opts)
328 ).
329
330 %print_binding(binding(Var,_,PPV)) :- format(' ~w = ~w~n',[Var,PPV]).
331
332 compute_unsat_core_if_requested(Predicate,Opts) :-
333 (member(disprover_option(unsat_core),Opts) -> compute_unsat_core(Opts,Predicate) ; true).
334 :- use_module(probsrc(preferences),[call_with_preference/3]).
335 :- use_module(probsrc(translate),[translate_predicate_into_machine/3,nested_print_bexpr_as_classicalb/1]).
336 compute_unsat_core(Opts,Predicate) :-
337 (member(unsat_core_algorithm/P,Opts)
338 -> call_with_preference(compute_unsat_core_aux(Predicate,CorePredicate),unsat_core_algorithm,P)
339 ; compute_unsat_core_aux(Predicate,CorePredicate)),
340 !,
341 print('UNSAT CORE: '),nl,print('--------'),nl,
342 (member(disprover_option(unsat_core_as_machine),Opts)
343 -> translate_predicate_into_machine(CorePredicate,'UnsatCore',ResultAtom),
344 write(ResultAtom),nl
345 ; nested_print_bexpr_as_classicalb(CorePredicate)),
346 nl, print('--------'),nl.
347
348 compute_unsat_core_aux(Predicate,CorePredicate) :-
349 print('Computing UNSAT CORE'),nl,
350 unsat_core(Predicate,CorePredicate). % from unsat_cores
351
352 :- use_module(probsrc(bsyntaxtree),[same_texpr/2]).
353 % check if two lists of conjuncts represent the same conjunction (modulo info fields)
354 same_conjunction_list(List1,List2) :-
355 same_length(List1,List2,_), % avoid possibly expensive comparisons if lists clearly different
356 % we could strip info field and the sort; but here we just want to catch the obvious case where the disprover is called with SelectedHyp = AllHyp
357 maplist(same_texpr,List1,List2).
358
359 :- use_module(probsrc(translate),[nested_print_sequent_as_classicalb/6,nested_print_bexpr_as_classicalb/1]).
360 write_hypothesis([]).
361 write_hypothesis([Hyp|Hyps]) :-
362 write(' & /* Hypothesis: */ '), nl,
363 %(debug_mode(on) -> write(Hyp), nl ; true),
364 nested_print_bexpr_as_classicalb(Hyp),
365 write_hypothesis(Hyps).
366
367 :- use_module(probsrc(tools_io),[safe_intelligent_open_file/3]).
368 export_po_as_machine(Filename,THypothesesList,TSelectedHypothesesList,TGoal,Infos) :-
369 %nested_print_sequent_as_classicalb(Stream,THypothesesList,TGoal,'ProofObligation_AllHyps',Infos),
370 format(user_output,'Exporting selected hypotheses to B machine: ~w~n',[Filename]),
371 safe_intelligent_open_file(Filename,write,Stream),
372 nested_print_sequent_as_classicalb(Stream,TSelectedHypothesesList,TGoal,
373 THypothesesList,'ProofObligation_SelectedHyps',Infos),
374 close(Stream).
375