1 | % (c) 2025-2025 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 | sat(Formula) :- start(E), sat(Formula,E). | |
107 | ||
108 | ||
109 | % Classic cases | |
110 | ||
111 | sat_not(false,_E). | |
112 | sat_not(true,_E) :- fail. | |
113 | sat_not(p(Property),E) :- \+(check_ap(Property,E)). | |
114 | sat_not(ap(Property),E) :- \+(check_ap(Property,E)). | |
115 | sat_not(and(F,_G),E) :- sat_not(F,E). | |
116 | sat_not(and(_F,G),E) :- sat_not(G,E). | |
117 | sat_not(or(F,G),E) :- sat_not(F,E),sat_not(G,E). | |
118 | sat_not(implies(F,G),E) :- sat(not(or(not(F),G)),E). | |
119 | sat_not(equivalence(F,G),E) :- sat_not(and(implies(F,G),implies(G,F)),E). | |
120 | sat_not(not(F),E) :- sat(F,E). | |
121 | ||
122 | %sat(Formula,State) :- write(sat(Formula,State)),nl,fail. | |
123 | sat(true,_E). | |
124 | sat(false,_E):-fail. | |
125 | sat(ap(Property),E) :- check_ap(Property,E). | |
126 | sat(p(Property),E) :- check_ap(Property,E). | |
127 | sat(and(F,G),E) :- sat(F,E), sat(G,E). | |
128 | sat(or(F,_G),E) :- sat(F,E). | |
129 | sat(or(_F,G),E) :- sat(G,E). | |
130 | sat(implies(F,G),E) :- sat(or(not(F),G),E). | |
131 | sat(equivalence(F,G),E) :- sat(and(implies(F,G),implies(G,F)),E). | |
132 | sat(not(F),E) :- probformula(_,_,_)\=F, sat_not(F,E). | |
133 | ||
134 | % Probabilistic-formula cases. Operator is =, < >,<= or >=. P is a number between 0 and 1 (or a Variable), | |
135 | % E is a state. | |
136 | % Check if the formula is nested | |
137 | ||
138 | sat(probformula(Operator,P,Ctl_formula),E) :- | |
139 | Ctl_formula = gk(_,_) -> | |
140 | sat_gk(probformula(Operator,P,Ctl_formula),E) | |
141 | ; Ctl_formula = g(_) -> | |
142 | sat_g(probformula(Operator,P,Ctl_formula),E) | |
143 | ; (node(Node) -> | |
144 | New_Node is Node +1, | |
145 | retract(node(Node)), | |
146 | assert(node(New_Node)), | |
147 | sat_node(probformula(Operator,P,Ctl_formula),E,New_Node) | |
148 | ; assert(node(0)), | |
149 | sat_node(probformula(Operator,P,Ctl_formula),E,0), | |
150 | retractall(node(_)) /*reinitialize nodes*/ | |
151 | ). | |
152 | ||
153 | sat(not(probformula(Operator,P,Ctl_formula)),E) :- | |
154 | negate_operator(Operator,NotOp), | |
155 | sat(probformula(NotOp,P,Ctl_formula),E). | |
156 | ||
157 | % Always bounded formula, we use the dual probabilistic event of fk(K,not(F)) | |
158 | sat(probformula(Operator,P,gk(K,F)),E) :- | |
159 | ground(P) -> | |
160 | Q is 1-P, | |
161 | (Operator = equal -> | |
162 | sat(probformula(Operator,Q,fk(K,not(F))),E) | |
163 | ; sat(not(probformula(Operator,Q,fk(K,not(F)))),E)) | |
164 | ; ((Operator = equal -> | |
165 | sat(probformula(Operator,Q,fk(K,not(F))),E) | |
166 | ; sat(not(probformula(Operator,Q,fk(K,not(F)))),E)), | |
167 | P is 1-Q) | |
168 | . | |
169 | ||
170 | % Always bounded formula, we use the dual probabilistic event of fk(K,not(F)) | |
171 | sat_gk(probformula(Operator,P,gk(K,F)),E) :- | |
172 | ground(P) -> | |
173 | Q is 1-P, | |
174 | (Operator = equal -> | |
175 | sat(probformula(Operator,Q,fk(K,not(F))),E) | |
176 | ; Operator = greater -> | |
177 | sat(probformula(less,Q,fk(K,not(F))),E) | |
178 | ; Operator = less -> | |
179 | sat(probformula(greater,Q,fk(K,not(F))),E) | |
180 | ; Operator = strictlygreater -> | |
181 | sat(probformula(strictlyless,Q,fk(K,not(F))),E) | |
182 | ; Operator = strictlyless -> | |
183 | sat(probformula(strictlygreater,Q,fk(K,not(F))),E)) | |
184 | ; ((Operator = equal -> | |
185 | sat(probformula(Operator,Q,fk(K,not(F))),E) | |
186 | ; Operator = greater -> | |
187 | sat(probformula(less,Q,fk(K,not(F))),E) | |
188 | ; Operator = less -> | |
189 | sat(probformula(greater,Q,fk(K,not(F))),E) | |
190 | ; Operator = strictlygreater -> | |
191 | sat(probformula(strictlyless,Q,fk(K,not(F))),E) | |
192 | ; Operator = strictlyless -> | |
193 | sat(probformula(strictlygreater,Q,fk(K,not(F))),E)), | |
194 | P is 1-Q) | |
195 | . | |
196 | ||
197 | % Always formula | |
198 | sat_g(probformula(Operator,P,g(F)),E) :- | |
199 | ground(P) -> | |
200 | Q is 1-P, | |
201 | (Operator = equal -> | |
202 | sat(probformula(Operator,Q,f(not(F))),E) | |
203 | ; Operator = greater -> | |
204 | sat(probformula(less,Q,f(not(F))),E) | |
205 | ; Operator = less -> | |
206 | sat(probformula(greater,Q,f(not(F))),E) | |
207 | ; Operator = strictlygreater -> | |
208 | sat(probformula(strictlyless,Q,f(not(F))),E) | |
209 | ; Operator = strictlyless -> | |
210 | sat(probformula(strictlygreater,Q,f(not(F))),E)) | |
211 | ; ((Operator = equal -> | |
212 | sat(probformula(Operator,Q,f(not(F))),E) | |
213 | ; Operator = greater -> | |
214 | sat(probformula(less,Q,f(not(F))),E) | |
215 | ; Operator = less -> | |
216 | sat(probformula(greater,Q,f(not(F))),E) | |
217 | ; Operator = strictlygreater -> | |
218 | sat(probformula(strictlyless,Q,f(not(F))),E) | |
219 | ; Operator = strictlyless -> | |
220 | sat(probformula(strictlygreater,Q,f(not(F))),E)), | |
221 | P is 1-Q) | |
222 | . | |
223 | ||
224 | % Check the type of the formula | |
225 | sat_node(probformula(Operator,P,Ctl_formula),E,Node) :- | |
226 | Ctl_formula = u(F,G) -> | |
227 | prob_calc(u(F,G),E,P_phi,Node), | |
228 | against(P_phi,P,Operator) | |
229 | ; Ctl_formula = f(G) -> | |
230 | prob_calc(u(true,G),E,P_phi,Node), | |
231 | against(P_phi,P,Operator) | |
232 | ; Ctl_formula = fk(K,G) -> | |
233 | sat_dynamic(probformula(Operator,P,uk(true,K,G)),E,Node) | |
234 | ; sat_dynamic(probformula(Operator,P,Ctl_formula),E,Node). | |
235 | ||
236 | % Use a different technic depending on the operator | |
237 | % This allow notably the calculation of a probability for the equal operator | |
238 | sat_dynamic(probformula(Operator,P,Ctl_formula),E,Node) :- | |
239 | ((Operator = greater ; Operator= strictlygreater) -> | |
240 | ground(P), | |
241 | prob_calc(Ctl_formula,E,P,Operator,Node) | |
242 | ||
243 | ; Operator = equal -> | |
244 | retractall(prob_current(Node,_)), | |
245 | (ground(P) -> | |
246 | prob_calc(Ctl_formula,E,P,equal,Node) | |
247 | ||
248 | ; (prob_calc(Ctl_formula,E,1.0,equal,Node) -> | |
249 | P=1.0 | |
250 | ; prob_current(Node,P))) | |
251 | ||
252 | ; Operator = less -> | |
253 | ground(P), | |
254 | \+(prob_calc(Ctl_formula,E,P,strictlygreater,Node)) | |
255 | ||
256 | ; Operator = strictlyless -> | |
257 | ground(P), | |
258 | \+(prob_calc(Ctl_formula,E,P,greater,Node)) | |
259 | ). | |
260 | ||
261 | % Compare different formulas using a specific operator | |
262 | ||
263 | % For the equal comparison, we compare the results using epsilon precision in case | |
264 | % of a given probability | |
265 | against(P_phi,ReferenceP,equal) :- !, | |
266 | (ground_number(ReferenceP,P) -> | |
267 | P_phi =< P + 0.00000000000000023, | |
268 | P =< P_phi + 0.00000000000000023 | |
269 | ; ReferenceP = P_phi % ReferenceP is an open variable | |
270 | ). | |
271 | against(P_phi,ReferenceP,less) :- | |
272 | ground_number(ReferenceP,P), !, | |
273 | P_phi =< P + 0.00000000000000023. % less | |
274 | against(P_phi,ReferenceP,greater) :- | |
275 | ground_number(ReferenceP,P), !, | |
276 | P_phi >= P - 0.00000000000000023. % greater | |
277 | against(P_phi,ReferenceP,strictlyless) :- | |
278 | ground_number(ReferenceP,P), !, | |
279 | P_phi + 0.00000000000000023 < P . % strictly less | |
280 | against(P_phi,ReferenceP,strictlygreater) :- | |
281 | ground_number(ReferenceP,P), !, | |
282 | P_phi - 0.00000000000000023 > P . % strictlygreater | |
283 | against(P_phi,ReferenceP,not(equal)) :- !, | |
284 | (ground_number(ReferenceP,P) -> | |
285 | (P_phi > P + 0.00000000000000023 ; | |
286 | P =< P_phi + 0.00000000000000023 | |
287 | ) | |
288 | ; % ReferenceP is an open variable | |
289 | add_warning(dtmc_model_checking,'Using symbolic variable for inequality: ',P_phi), | |
290 | (P_phi < 1-0.00000000000000023 | |
291 | -> ReferenceP = P_phi + 0.00000000000000023 | |
292 | ; ReferenceP = P_phi - 0.00000000000000023 | |
293 | ) | |
294 | ). | |
295 | against(P_phi,P,not(less)) :- !, against(P_phi,P,strictlygreater). | |
296 | against(P_phi,P,not(greater)) :- !, against(P_phi,P,strictlyless). | |
297 | against(P_phi,P,not(strictlyless)) :- !, against(P_phi,P,greater). | |
298 | against(P_phi,P,not(strictlygreater)) :- !, against(P_phi,P,less). | |
299 | against(P1,P2,Op) :- add_internal_error('Illegal comparison operator: ',against(P1,P2,Op)),fail. | |
300 | ||
301 | negate_operator(not(Op),R) :- !, R=Op. | |
302 | negate_operator(Op,not(Op)). | |
303 | ||
304 | :- use_module(probsrc(tools),[safe_number_codes/2]). | |
305 | % TODO: in future fully pre-process AST of formula once before launching model checker | |
306 | ground_number(P,Res) :- number(P),!,Res=P. | |
307 | ground_number(P,Res) :- atom(P), atom_codes(P,Codes), % convert atom from parser into number | |
308 | safe_number_codes(Nr,Codes),!,Res=Nr. | |
309 | ||
310 | ||
311 | % Next formula | |
312 | :- dynamic prob_current/2. | |
313 | ||
314 | prob_calc(x(F),E,P_phi,Operator,Node) :- | |
315 | retractall(prob_current(Node,_)), | |
316 | assert(prob_current(Node,0.0)), | |
317 | (prob_calc_sub(x(F),E,P_phi,Operator,Node) -> | |
318 | true | |
319 | ; against(0.0,P_phi,Operator)),!. | |
320 | ||
321 | % Until Bounded formula | |
322 | prob_calc(uk(F,K,G),E,P_phi,Operator,Node) :- | |
323 | retractall(prob_current(Node,_)), | |
324 | assert(prob_current(Node,0.0)), | |
325 | (sat(G,E) -> | |
326 | retractall(prob_current(Node,_)), | |
327 | assert(prob_current(Node,1.0)), | |
328 | against(1.0,P_phi,Operator) | |
329 | ; sat(F,E) -> | |
330 | (prob_calc_sub(uk(F,K,G),E,P_phi,1.0,Operator,Node) -> | |
331 | true | |
332 | ; against(0.0,P_phi,Operator)) | |
333 | ; against(0.0,P_phi,Operator)),!. | |
334 | ||
335 | % Until formula | |
336 | % For this formula we have to calculate the probability for | |
337 | % all states | |
338 | prob_calc(u(F,G),E,P_phi,Node) :- | |
339 | retractall(table_prob0(_,_,Node)), | |
340 | retractall(table_prob1(_,_,Node)), | |
341 | prob_calc_u1(F,G,List_S,P_vect,Node), | |
342 | state(E), | |
343 | (find_prob_u(List_S,P_vect,E,P) -> P_phi=P | |
344 | ; P_phi=0.0 | |
345 | ). | |
346 | ||
347 | ||
348 | %******************************************************* | |
349 | ||
350 | % recursion for the next formula | |
351 | prob_calc_sub(x(F),E,P_phi,Operator,Node) :- | |
352 | transition_probability(E,S,P), | |
353 | sat(F,S), | |
354 | prob_current(Node,Previous_P), | |
355 | Current_prob is Previous_P +P, | |
356 | retract(prob_current(Node,Previous_P)), | |
357 | assert(prob_current(Node,Current_prob)), | |
358 | against(Current_prob,P_phi,Operator). | |
359 | ||
360 | % recursion for the bounded until formula | |
361 | prob_calc_sub(uk(F,K_new,G),E,P_phi,P_trace,Operator,Node) :- | |
362 | (sat(G,E) -> | |
363 | prob_current(Node,P), | |
364 | P_new is P+P_trace, | |
365 | retract(prob_current(Node,P)), | |
366 | assert(prob_current(Node,P_new)), | |
367 | against(P_new,P_phi,Operator) | |
368 | ; K_new > 0, | |
369 | transition_probability(E,S,P_trans), | |
370 | sat(F,S), | |
371 | K is K_new -1, | |
372 | P_trace_new is P_trace*P_trans, | |
373 | prob_calc_sub(uk(F,K,G),S,P_phi,P_trace_new,Operator,Node) | |
374 | ). | |
375 | ||
376 | % 1rst precomputation for the until formula | |
377 | ||
378 | :- dynamic table_prob0/3. | |
379 | ||
380 | prob0(_F,_G,E,Node) :- | |
381 | table_prob0(E,true,Node),!. | |
382 | ||
383 | prob0(_F,G,E,Node) :- | |
384 | sat(G,E), | |
385 | !, | |
386 | asserta(table_prob0(E,true,Node)). | |
387 | ||
388 | prob0(F,G,E1,Node) :- | |
389 | sat(F,E1), | |
390 | assertz(table_prob0(E1,computing,Node)), | |
391 | trans(E1,E2,_P,_TransId), | |
392 | \+ table_prob0(E2,computing,Node), | |
393 | (prob0(F,G,E2,Node) -> | |
394 | asserta(table_prob0(E1,true,Node)), | |
395 | retract(table_prob0(E1,computing,Node)) | |
396 | ),!. | |
397 | ||
398 | search_prob0(F,G,E,Node):- | |
399 | retractall(table_prob0(_,computing,Node)), | |
400 | prob0(F,G,E,Node). | |
401 | ||
402 | % 2nd precomputation for the until formula | |
403 | :- dynamic table_prob1/3. | |
404 | ||
405 | prob1(_F,_G,E,Node) :- | |
406 | table_prob1(E,true,Node),!. | |
407 | ||
408 | prob1(_F,_G,E,Node) :- | |
409 | \+(table_prob0(E,true,Node)), | |
410 | !, | |
411 | asserta(table_prob1(E,true,Node)). | |
412 | ||
413 | prob1(F,G,E1,Node) :- | |
414 | sat(F,E1), | |
415 | \+(sat(G,E1)), | |
416 | assert(table_prob1(E1,computing,Node)), | |
417 | trans(E1,E2,_P,_), | |
418 | \+ table_prob1(E2,computing,Node), | |
419 | (prob1(F,G,E2,Node) -> | |
420 | asserta(table_prob1(E1,true,Node)), | |
421 | retract(table_prob1(E1,computing,Node)) | |
422 | ),!. | |
423 | ||
424 | search_prob1(F,G,E,Node):- | |
425 | retractall(table_prob1(_,computing,Node)), | |
426 | prob1(F,G,E,Node). | |
427 | ||
428 | % P_vect is the vector of non-null probabilities | |
429 | % for the until formula | |
430 | prob_calc_u1(F,G,List_S,P_vect,Node) :- | |
431 | findall(S,(state(S),search_prob0(F,G,S,Node)),List_S), | |
432 | findall(E,(state(E),search_prob1(F,G,E,Node)),_List), | |
433 | prob_calc_u2(F,G,List_S,List_S,P_vect,P_vect,Node). | |
434 | ||
435 | prob_calc_u2(_F,_G,[],_List_S,[],_P_vect,_Node). | |
436 | prob_calc_u2(F,G,[S|List_S_explored],List_S,[P_phi|P_vect_explored],P_vect,Node) :- | |
437 | (table_prob1(S,true,Node) -> prob_calc_u3(S,List_S,P_phi,P_vect) | |
438 | ; P_phi=1.0 | |
439 | ), | |
440 | prob_calc_u2(F,G,List_S_explored,List_S,P_vect_explored,P_vect,Node). | |
441 | ||
442 | prob_calc_u3(_S,[],0.0,[]). | |
443 | prob_calc_u3(S,[E|List_S],P_phi_new,[P|P_vect]) :- | |
444 | prob_calc_u3(S,List_S,P_phi,P_vect), | |
445 | (transition_probability(S,E,P_trans) -> {P_phi_new = P*P_trans + P_phi} | |
446 | ; P_phi_new=P_phi | |
447 | ). | |
448 | ||
449 | find_prob_u([E],[P],E,P). | |
450 | find_prob_u([S1,S2|List_S],[Prob1,Prob2|P_vect],E,P) :- | |
451 | (E=S1,P=Prob1) | |
452 | ; find_prob_u([S2|List_S],[Prob2|P_vect],E,P). |