1 % (c) 2025-2026 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
7 % This is a model-checker verifying Probabilistic Computation
8 % Tree Logic (PCTL) formulas over Discrete-Time Markov Chains (DTMC)
9 % written by Honore Marmion
10
11 % Working on SICStus prolog 4.10
12
13 %###############################################
14
15 :- module(dtmc_model_checking,[pctl_model_check/4
16 % sat/1, sat/2,
17 % search_prob0/4,prob0/4,prob1/4,table_prob0/3,state/1
18 ]).
19 :- use_module(library(clpr),[{}/1]).
20
21
22 :- use_module(probsrc(error_manager),[add_error/3, add_internal_error/2, add_warning/3]).
23 :- use_module(probltlsrc(ltl_tools),[temporal_parser/3]).
24 :- use_module(probsrc(state_space),[find_initialised_states/1, current_state_id/1]).
25 :- use_module(probsrc(tools),[start_ms_timer/1, stop_ms_timer_with_msg/2]).
26
27 pctl_model_check(Formula,_MaxNodes,Mode,Res) :-
28 (temporal_parser(Formula,pctl,PCtlFormula) -> true ; add_error(ctl,'PCTL Parser failed: ',Formula),fail),
29 %set_max_nr_of_new_impl_trans_nodes(MaxNodes),
30 % TODO: provide better feedback, extract probability for P={p} formulas
31 (pre_process_formula(PCtlFormula,ProcessedFormula,Bindings,[]) -> true
32 ; add_error(dtmc_model_checking,'Could not pre-process: ',PCtlFormula),
33 ProcessedFormula = PCtlFormula
34 ),
35 (Mode= specific_node(ID) -> Start=ID
36 ; Mode = starthere -> current_state_id(Start)
37 ; Mode = init -> find_initialised_states(SN), member(Start,SN) % TODO try out all start nodes??
38 ; add_error(dtmc_model_checking,'Illegal starting mode (init, starthere supported):',Mode),
39 Start = root
40 ),
41 format('Checking PCTL formula from state ~w : ~w~n AST: ~w~n Open Probability Variables: ~w~n',[Start,Formula,ProcessedFormula,Bindings]),
42 reset_tables,
43 start_ms_timer(T1),
44 (sat(ProcessedFormula,Start)
45 -> (Bindings=[] -> Res=true ; Res = solution(Bindings))
46 ; Res = false),
47 stop_ms_timer_with_msg(T1,'PTCTL model checking').
48
49 reset_tables :- retractall(table_prob0(_,_,_)),retractall(node(_)), retractall(prob_current(_,_)).
50
51 pre_process_formula(true,true) --> [].
52 pre_process_formula(false,false) --> [].
53 pre_process_formula(ap(P),ap(P)) --> [].
54 pre_process_formula(p(P),p(P)) --> [].
55 pre_process_formula(probformula(Operator,RefProbAtom,Ctl_formula),
56 probformula(Operator,RefProbNr,Ctl_formula)) -->
57 ({ground_number(RefProbAtom,RefProbNr)} -> []
58 ; {Operator=equal} -> [RefProbAtom/RefProbNr]
59 ; {add_error(dtmc_model_checking,'Illegal reference probability for operator (only equal is supported for symbolic values):',RefProbAtom/Operator)},
60 [RefProbAtom/RefProbNr]
61 ). % TODO: pre_process_path_formula
62 pre_process_formula(and(F,G),and(PF,PG)) -->
63 pre_process_formula(F,PF),
64 pre_process_formula(G,PG).
65 pre_process_formula(or(F,G),and(PF,PG)) -->
66 pre_process_formula(F,PF),
67 pre_process_formula(G,PG).
68 pre_process_formula(implies(F,G),and(PF,PG)) -->
69 pre_process_formula(F,PF),
70 pre_process_formula(G,PG).
71 pre_process_formula(not(F),not(PF)) -->
72 pre_process_formula(F,PF).
73
74 %pre_process_path_formula(u(F,G),u(PF,PG)) -->
75 % pre_process_formula(F,PF),
76 % pre_process_formula(G,PG).
77
78
79 % Interface predicates defining the system to be checked
80
81 :- use_module(probsrc(xtl_interface),[xtl_transition/4]).
82 :- use_module(probltlsrc(ltl_propositions), [trans/4, check_ap/2]). % check_transition_pred/5
83 :- use_module(probsrc(state_space),[visited_expression/2, visited_expression_id/1]).
84
85 % TODO: move to specfile and extend to work with B pragmas,...
86 transition_probability(From,To,Prob) :- trans(From,To,_,_),
87 visited_expression(From,FS),
88 visited_expression(To,TS),
89 xtl_transition(FS,_,TS,Infos),
90 (member(probability/X,Infos) -> Prob=X ; write(no_probability(From,To,Infos)),nl,fail).
91
92 state(X) :- visited_expression_id(X).
93
94 % example public_examples/XTL/markov/SimpleMarkov.P
95 % ?- use_module(extension('markov/dtmc_model_checking.pl')).
96 % ?- sat(prob_formula(eq,P,f(p(xtl_predicate_check(finished))))).
97
98 %PCTL Model-checking
99
100 % This term allow to handle nested formulas in case of dynamic model-checking
101 :- dynamic node/1.
102
103 %start(root).
104 start(ID) :- find_initialised_states(I), member(ID,I).
105
106 :- public sat/1.
107 sat(Formula) :- start(E), sat(Formula,E).
108
109
110 % Classic cases
111
112 sat_not(false,_E).
113 sat_not(true,_E) :- fail.
114 sat_not(p(Property),E) :- \+(check_ap(Property,E)).
115 sat_not(ap(Property),E) :- \+(check_ap(Property,E)).
116 sat_not(and(F,_G),E) :- sat_not(F,E).
117 sat_not(and(_F,G),E) :- sat_not(G,E).
118 sat_not(or(F,G),E) :- sat_not(F,E),sat_not(G,E).
119 sat_not(implies(F,G),E) :- sat(not(or(not(F),G)),E).
120 sat_not(equivalence(F,G),E) :- sat_not(and(implies(F,G),implies(G,F)),E).
121 sat_not(not(F),E) :- sat(F,E).
122
123 %sat(Formula,State) :- write(sat(Formula,State)),nl,fail.
124 sat(true,_E).
125 sat(false,_E):-fail.
126 sat(ap(Property),E) :- check_ap(Property,E).
127 sat(p(Property),E) :- check_ap(Property,E).
128 sat(and(F,G),E) :- sat(F,E), sat(G,E).
129 sat(or(F,_G),E) :- sat(F,E).
130 sat(or(_F,G),E) :- sat(G,E).
131 sat(implies(F,G),E) :- sat(or(not(F),G),E).
132 sat(equivalence(F,G),E) :- sat(and(implies(F,G),implies(G,F)),E).
133 sat(not(F),E) :- probformula(_,_,_)\=F, sat_not(F,E).
134
135 % Probabilistic-formula cases. Operator is =, < >,<= or >=. P is a number between 0 and 1 (or a Variable),
136 % E is a state.
137 % Check if the formula is nested
138
139 sat(probformula(Operator,P,Ctl_formula),E) :-
140 Ctl_formula = gk(_,_) ->
141 sat_gk(probformula(Operator,P,Ctl_formula),E)
142 ; Ctl_formula = g(_) ->
143 sat_g(probformula(Operator,P,Ctl_formula),E)
144 ; (node(Node) ->
145 New_Node is Node +1,
146 retract(node(Node)),
147 assert(node(New_Node)),
148 sat_node(probformula(Operator,P,Ctl_formula),E,New_Node)
149 ; assert(node(0)),
150 sat_node(probformula(Operator,P,Ctl_formula),E,0),
151 retractall(node(_)) /*reinitialize nodes*/
152 ).
153
154 sat(not(probformula(Operator,P,Ctl_formula)),E) :-
155 negate_operator(Operator,NotOp),
156 sat(probformula(NotOp,P,Ctl_formula),E).
157
158 % Always bounded formula, we use the dual probabilistic event of fk(K,not(F))
159 sat(probformula(Operator,P,gk(K,F)),E) :-
160 ground(P) ->
161 Q is 1-P,
162 (Operator = equal ->
163 sat(probformula(Operator,Q,fk(K,not(F))),E)
164 ; sat(not(probformula(Operator,Q,fk(K,not(F)))),E))
165 ; ((Operator = equal ->
166 sat(probformula(Operator,Q,fk(K,not(F))),E)
167 ; sat(not(probformula(Operator,Q,fk(K,not(F)))),E)),
168 P is 1-Q)
169 .
170
171 % Always bounded formula, we use the dual probabilistic event of fk(K,not(F))
172 sat_gk(probformula(Operator,P,gk(K,F)),E) :-
173 ground(P) ->
174 Q is 1-P,
175 (Operator = equal ->
176 sat(probformula(Operator,Q,fk(K,not(F))),E)
177 ; Operator = greater ->
178 sat(probformula(less,Q,fk(K,not(F))),E)
179 ; Operator = less ->
180 sat(probformula(greater,Q,fk(K,not(F))),E)
181 ; Operator = strictlygreater ->
182 sat(probformula(strictlyless,Q,fk(K,not(F))),E)
183 ; Operator = strictlyless ->
184 sat(probformula(strictlygreater,Q,fk(K,not(F))),E))
185 ; ((Operator = equal ->
186 sat(probformula(Operator,Q,fk(K,not(F))),E)
187 ; Operator = greater ->
188 sat(probformula(less,Q,fk(K,not(F))),E)
189 ; Operator = less ->
190 sat(probformula(greater,Q,fk(K,not(F))),E)
191 ; Operator = strictlygreater ->
192 sat(probformula(strictlyless,Q,fk(K,not(F))),E)
193 ; Operator = strictlyless ->
194 sat(probformula(strictlygreater,Q,fk(K,not(F))),E)),
195 P is 1-Q)
196 .
197
198 % Always formula
199 sat_g(probformula(Operator,P,g(F)),E) :-
200 ground(P) ->
201 Q is 1-P,
202 (Operator = equal ->
203 sat(probformula(Operator,Q,f(not(F))),E)
204 ; Operator = greater ->
205 sat(probformula(less,Q,f(not(F))),E)
206 ; Operator = less ->
207 sat(probformula(greater,Q,f(not(F))),E)
208 ; Operator = strictlygreater ->
209 sat(probformula(strictlyless,Q,f(not(F))),E)
210 ; Operator = strictlyless ->
211 sat(probformula(strictlygreater,Q,f(not(F))),E))
212 ; ((Operator = equal ->
213 sat(probformula(Operator,Q,f(not(F))),E)
214 ; Operator = greater ->
215 sat(probformula(less,Q,f(not(F))),E)
216 ; Operator = less ->
217 sat(probformula(greater,Q,f(not(F))),E)
218 ; Operator = strictlygreater ->
219 sat(probformula(strictlyless,Q,f(not(F))),E)
220 ; Operator = strictlyless ->
221 sat(probformula(strictlygreater,Q,f(not(F))),E)),
222 P is 1-Q)
223 .
224
225 % Check the type of the formula
226 sat_node(probformula(Operator,P,Ctl_formula),E,Node) :-
227 Ctl_formula = u(F,G) ->
228 prob_calc(u(F,G),E,P_phi,Node),
229 against(P_phi,P,Operator)
230 ; Ctl_formula = f(G) ->
231 prob_calc(u(true,G),E,P_phi,Node),
232 against(P_phi,P,Operator)
233 ; Ctl_formula = fk(K,G) ->
234 sat_dynamic(probformula(Operator,P,uk(true,K,G)),E,Node)
235 ; sat_dynamic(probformula(Operator,P,Ctl_formula),E,Node).
236
237 % Use a different technic depending on the operator
238 % This allow notably the calculation of a probability for the equal operator
239 sat_dynamic(probformula(Operator,P,Ctl_formula),E,Node) :-
240 ((Operator = greater ; Operator= strictlygreater) ->
241 ground(P),
242 prob_calc(Ctl_formula,E,P,Operator,Node)
243
244 ; Operator = equal ->
245 retractall(prob_current(Node,_)),
246 (ground(P) ->
247 prob_calc(Ctl_formula,E,P,equal,Node)
248
249 ; (prob_calc(Ctl_formula,E,1.0,equal,Node) ->
250 P=1.0
251 ; prob_current(Node,P)))
252
253 ; Operator = less ->
254 ground(P),
255 \+(prob_calc(Ctl_formula,E,P,strictlygreater,Node))
256
257 ; Operator = strictlyless ->
258 ground(P),
259 \+(prob_calc(Ctl_formula,E,P,greater,Node))
260 ).
261
262 % Compare different formulas using a specific operator
263
264 % For the equal comparison, we compare the results using epsilon precision in case
265 % of a given probability
266 against(P_phi,ReferenceP,equal) :- !,
267 (ground_number(ReferenceP,P) ->
268 P_phi =< P + 0.00000000000000023,
269 P =< P_phi + 0.00000000000000023
270 ; ReferenceP = P_phi % ReferenceP is an open variable
271 ).
272 against(P_phi,ReferenceP,less) :-
273 ground_number(ReferenceP,P), !,
274 P_phi =< P + 0.00000000000000023. % less
275 against(P_phi,ReferenceP,greater) :-
276 ground_number(ReferenceP,P), !,
277 P_phi >= P - 0.00000000000000023. % greater
278 against(P_phi,ReferenceP,strictlyless) :-
279 ground_number(ReferenceP,P), !,
280 P_phi + 0.00000000000000023 < P . % strictly less
281 against(P_phi,ReferenceP,strictlygreater) :-
282 ground_number(ReferenceP,P), !,
283 P_phi - 0.00000000000000023 > P . % strictlygreater
284 against(P_phi,ReferenceP,not(equal)) :- !,
285 (ground_number(ReferenceP,P) ->
286 (P_phi > P + 0.00000000000000023 ;
287 P =< P_phi + 0.00000000000000023
288 )
289 ; % ReferenceP is an open variable
290 add_warning(dtmc_model_checking,'Using symbolic variable for inequality: ',P_phi),
291 (P_phi < 1-0.00000000000000023
292 -> ReferenceP = P_phi + 0.00000000000000023
293 ; ReferenceP = P_phi - 0.00000000000000023
294 )
295 ).
296 against(P_phi,P,not(less)) :- !, against(P_phi,P,strictlygreater).
297 against(P_phi,P,not(greater)) :- !, against(P_phi,P,strictlyless).
298 against(P_phi,P,not(strictlyless)) :- !, against(P_phi,P,greater).
299 against(P_phi,P,not(strictlygreater)) :- !, against(P_phi,P,less).
300 against(P1,P2,Op) :- add_internal_error('Illegal comparison operator: ',against(P1,P2,Op)),fail.
301
302 negate_operator(not(Op),R) :- !, R=Op.
303 negate_operator(Op,not(Op)).
304
305 :- use_module(probsrc(tools),[safe_number_codes/2]).
306 % TODO: in future fully pre-process AST of formula once before launching model checker
307 ground_number(P,Res) :- number(P),!,Res=P.
308 ground_number(P,Res) :- atom(P), atom_codes(P,Codes), % convert atom from parser into number
309 safe_number_codes(Nr,Codes),!,Res=Nr.
310
311
312 % Next formula
313 :- dynamic prob_current/2.
314
315 prob_calc(x(F),E,P_phi,Operator,Node) :-
316 retractall(prob_current(Node,_)),
317 assert(prob_current(Node,0.0)),
318 (prob_calc_sub(x(F),E,P_phi,Operator,Node) ->
319 true
320 ; against(0.0,P_phi,Operator)),!.
321
322 % Until Bounded formula
323 prob_calc(uk(F,K,G),E,P_phi,Operator,Node) :-
324 retractall(prob_current(Node,_)),
325 assert(prob_current(Node,0.0)),
326 (sat(G,E) ->
327 retractall(prob_current(Node,_)),
328 assert(prob_current(Node,1.0)),
329 against(1.0,P_phi,Operator)
330 ; sat(F,E) ->
331 (prob_calc_sub(uk(F,K,G),E,P_phi,1.0,Operator,Node) ->
332 true
333 ; against(0.0,P_phi,Operator))
334 ; against(0.0,P_phi,Operator)),!.
335
336 % Until formula
337 % For this formula we have to calculate the probability for
338 % all states
339 prob_calc(u(F,G),E,P_phi,Node) :-
340 retractall(table_prob0(_,_,Node)),
341 retractall(table_prob1(_,_,Node)),
342 prob_calc_u1(F,G,List_S,P_vect,Node),
343 state(E),
344 (find_prob_u(List_S,P_vect,E,P) -> P_phi=P
345 ; P_phi=0.0
346 ).
347
348
349 %*******************************************************
350
351 % recursion for the next formula
352 prob_calc_sub(x(F),E,P_phi,Operator,Node) :-
353 transition_probability(E,S,P),
354 sat(F,S),
355 prob_current(Node,Previous_P),
356 Current_prob is Previous_P +P,
357 retract(prob_current(Node,Previous_P)),
358 assert(prob_current(Node,Current_prob)),
359 against(Current_prob,P_phi,Operator).
360
361 % recursion for the bounded until formula
362 prob_calc_sub(uk(F,K_new,G),E,P_phi,P_trace,Operator,Node) :-
363 (sat(G,E) ->
364 prob_current(Node,P),
365 P_new is P+P_trace,
366 retract(prob_current(Node,P)),
367 assert(prob_current(Node,P_new)),
368 against(P_new,P_phi,Operator)
369 ; K_new > 0,
370 transition_probability(E,S,P_trans),
371 sat(F,S),
372 K is K_new -1,
373 P_trace_new is P_trace*P_trans,
374 prob_calc_sub(uk(F,K,G),S,P_phi,P_trace_new,Operator,Node)
375 ).
376
377 % 1rst precomputation for the until formula
378
379 :- dynamic table_prob0/3.
380
381 prob0(_F,_G,E,Node) :-
382 table_prob0(E,true,Node),!.
383
384 prob0(_F,G,E,Node) :-
385 sat(G,E),
386 !,
387 asserta(table_prob0(E,true,Node)).
388
389 prob0(F,G,E1,Node) :-
390 sat(F,E1),
391 assertz(table_prob0(E1,computing,Node)),
392 trans(E1,E2,_P,_TransId),
393 \+ table_prob0(E2,computing,Node),
394 (prob0(F,G,E2,Node) ->
395 asserta(table_prob0(E1,true,Node)),
396 retract(table_prob0(E1,computing,Node))
397 ),!.
398
399 search_prob0(F,G,E,Node):-
400 retractall(table_prob0(_,computing,Node)),
401 prob0(F,G,E,Node).
402
403 % 2nd precomputation for the until formula
404 :- dynamic table_prob1/3.
405
406 prob1(_F,_G,E,Node) :-
407 table_prob1(E,true,Node),!.
408
409 prob1(_F,_G,E,Node) :-
410 \+(table_prob0(E,true,Node)),
411 !,
412 asserta(table_prob1(E,true,Node)).
413
414 prob1(F,G,E1,Node) :-
415 sat(F,E1),
416 \+(sat(G,E1)),
417 assert(table_prob1(E1,computing,Node)),
418 trans(E1,E2,_P,_),
419 \+ table_prob1(E2,computing,Node),
420 (prob1(F,G,E2,Node) ->
421 asserta(table_prob1(E1,true,Node)),
422 retract(table_prob1(E1,computing,Node))
423 ),!.
424
425 search_prob1(F,G,E,Node):-
426 retractall(table_prob1(_,computing,Node)),
427 prob1(F,G,E,Node).
428
429 % P_vect is the vector of non-null probabilities
430 % for the until formula
431 prob_calc_u1(F,G,List_S,P_vect,Node) :-
432 findall(S,(state(S),search_prob0(F,G,S,Node)),List_S),
433 findall(E,(state(E),search_prob1(F,G,E,Node)),_List),
434 prob_calc_u2(F,G,List_S,List_S,P_vect,P_vect,Node).
435
436 prob_calc_u2(_F,_G,[],_List_S,[],_P_vect,_Node).
437 prob_calc_u2(F,G,[S|List_S_explored],List_S,[P_phi|P_vect_explored],P_vect,Node) :-
438 (table_prob1(S,true,Node) -> prob_calc_u3(S,List_S,P_phi,P_vect)
439 ; P_phi=1.0
440 ),
441 prob_calc_u2(F,G,List_S_explored,List_S,P_vect_explored,P_vect,Node).
442
443 prob_calc_u3(_S,[],0.0,[]).
444 prob_calc_u3(S,[E|List_S],P_phi_new,[P|P_vect]) :-
445 prob_calc_u3(S,List_S,P_phi,P_vect),
446 (transition_probability(S,E,P_trans) -> {P_phi_new = P*P_trans + P_phi}
447 ; P_phi_new=P_phi
448 ).
449
450 find_prob_u([E],[P],E,P).
451 find_prob_u([S1,S2|List_S],[Prob1,Prob2|P_vect],E,P) :-
452 (E=S1,P=Prob1)
453 ; find_prob_u([S2|List_S],[Prob2|P_vect],E,P).