1use crate::NQuin;
2
3pub const MAX_CTL_STATES: usize = 256;
10
11#[inline]
12fn satisfies(graph: &[NQuin], state: u64, holds: u64, prop: u64) -> bool {
13 graph
14 .iter()
15 .any(|q| q.subject == state && q.predicate == holds && q.object == prop)
16}
17
18pub fn exists_finally(graph: &[NQuin], start: u64, prop: u64, next: u64, holds: u64) -> bool {
21 if satisfies(graph, start, holds, prop) {
22 return true;
23 }
24 let mut stack = [0u64; MAX_CTL_STATES];
25 let mut slen = 1usize;
26 stack[0] = start;
27 let mut visited = [0u64; MAX_CTL_STATES];
28 let mut vlen = 1usize;
29 visited[0] = start;
30 while slen > 0 {
31 slen -= 1;
32 let node = stack[slen];
33 for q in graph {
34 if q.subject != node || q.predicate != next {
35 continue;
36 }
37 let s2 = q.object;
38 if satisfies(graph, s2, holds, prop) {
39 return true;
40 }
41 let mut seen = false;
42 for &v in visited.iter().take(vlen) {
43 if v == s2 {
44 seen = true;
45 break;
46 }
47 }
48 if !seen && vlen < MAX_CTL_STATES && slen < MAX_CTL_STATES {
49 visited[vlen] = s2;
50 vlen += 1;
51 stack[slen] = s2;
52 slen += 1;
53 }
54 }
55 }
56 false
57}
58
59pub fn always_globally(graph: &[NQuin], start: u64, inv: u64, next: u64, holds: u64) -> bool {
62 if !satisfies(graph, start, holds, inv) {
63 return false;
64 }
65 let mut stack = [0u64; MAX_CTL_STATES];
66 let mut slen = 1usize;
67 stack[0] = start;
68 let mut visited = [0u64; MAX_CTL_STATES];
69 let mut vlen = 1usize;
70 visited[0] = start;
71 while slen > 0 {
72 slen -= 1;
73 let node = stack[slen];
74 for q in graph {
75 if q.subject != node || q.predicate != next {
76 continue;
77 }
78 let s2 = q.object;
79 let mut seen = false;
80 for &v in visited.iter().take(vlen) {
81 if v == s2 {
82 seen = true;
83 break;
84 }
85 }
86 if seen {
87 continue;
88 }
89 if !satisfies(graph, s2, holds, inv) {
90 return false; }
92 if vlen < MAX_CTL_STATES && slen < MAX_CTL_STATES {
93 visited[vlen] = s2;
94 vlen += 1;
95 stack[slen] = s2;
96 slen += 1;
97 }
98 }
99 }
100 true
101}
102
103pub fn exists_next(graph: &[NQuin], start: u64, prop: u64, next: u64, holds: u64) -> bool {
105 graph.iter().any(|q| {
106 q.subject == start && q.predicate == next && satisfies(graph, q.object, holds, prop)
107 })
108}
109
110pub fn always_next(graph: &[NQuin], start: u64, prop: u64, next: u64, holds: u64) -> bool {
112 graph
113 .iter()
114 .filter(|q| q.subject == start && q.predicate == next)
115 .all(|q| satisfies(graph, q.object, holds, prop))
116}
117
118pub fn exists_until(
121 graph: &[NQuin],
122 start: u64,
123 phi: u64,
124 psi: u64,
125 next: u64,
126 holds: u64,
127) -> bool {
128 if satisfies(graph, start, holds, psi) {
129 return true;
130 }
131 if !satisfies(graph, start, holds, phi) {
132 return false;
133 }
134 let mut stack = [0u64; MAX_CTL_STATES];
135 let mut sl = 1usize;
136 stack[0] = start;
137 let mut vis = [0u64; MAX_CTL_STATES];
138 let mut vl = 1usize;
139 vis[0] = start;
140 while sl > 0 {
141 sl -= 1;
142 let node = stack[sl];
143 for q in graph {
144 if q.subject != node || q.predicate != next {
145 continue;
146 }
147 let s2 = q.object;
148 if satisfies(graph, s2, holds, psi) {
149 return true;
150 }
151 if satisfies(graph, s2, holds, phi)
152 && !vis[..vl].contains(&s2)
153 && vl < MAX_CTL_STATES
154 && sl < MAX_CTL_STATES
155 {
156 vis[vl] = s2;
157 vl += 1;
158 stack[sl] = s2;
159 sl += 1;
160 }
161 }
162 }
163 false
164}
165
166fn reachable_states(
169 graph: &[NQuin],
170 start: u64,
171 next: u64,
172 out: &mut [u64; MAX_CTL_STATES],
173) -> usize {
174 let mut vl = 1usize;
175 out[0] = start;
176 let mut i = 0usize;
177 while i < vl {
178 let node = out[i];
179 i += 1;
180 for q in graph {
181 if q.subject == node && q.predicate == next {
182 let s2 = q.object;
183 if !out[..vl].contains(&s2) && vl < MAX_CTL_STATES {
184 out[vl] = s2;
185 vl += 1;
186 }
187 }
188 }
189 }
190 vl
191}
192
193#[inline]
194fn idx_of(states: &[u64], s: u64) -> Option<usize> {
195 states.iter().position(|&x| x == s)
196}
197
198pub fn exists_globally(graph: &[NQuin], start: u64, prop: u64, next: u64, holds: u64) -> bool {
202 let mut states = [0u64; MAX_CTL_STATES];
203 let n = reachable_states(graph, start, next, &mut states);
204 let mut alive = [false; MAX_CTL_STATES];
205 for i in 0..n {
206 alive[i] = satisfies(graph, states[i], holds, prop);
207 }
208 loop {
209 let mut changed = false;
210 for i in 0..n {
211 if !alive[i] {
212 continue;
213 }
214 let mut has_alive_succ = false;
216 for q in graph {
217 if q.subject == states[i] && q.predicate == next {
218 if let Some(j) = idx_of(&states[..n], q.object) {
219 if alive[j] {
220 has_alive_succ = true;
221 break;
222 }
223 }
224 }
225 }
226 if !has_alive_succ {
227 alive[i] = false;
228 changed = true;
229 }
230 }
231 if !changed {
232 break;
233 }
234 }
235 idx_of(&states[..n], start)
236 .map(|i| alive[i])
237 .unwrap_or(false)
238}
239
240pub fn all_finally(graph: &[NQuin], start: u64, prop: u64, next: u64, holds: u64) -> bool {
244 let mut states = [0u64; MAX_CTL_STATES];
245 let n = reachable_states(graph, start, next, &mut states);
246 let mut is_af = [false; MAX_CTL_STATES];
247 for i in 0..n {
248 is_af[i] = satisfies(graph, states[i], holds, prop);
249 }
250 loop {
251 let mut changed = false;
252 for i in 0..n {
253 if is_af[i] {
254 continue;
255 }
256 let mut any_succ = false;
257 let mut all_af = true;
258 for q in graph {
259 if q.subject == states[i] && q.predicate == next {
260 any_succ = true;
261 match idx_of(&states[..n], q.object) {
262 Some(j) if is_af[j] => {}
263 _ => {
264 all_af = false;
265 break;
266 }
267 }
268 }
269 }
270 if any_succ && all_af {
271 is_af[i] = true;
272 changed = true;
273 }
274 }
275 if !changed {
276 break;
277 }
278 }
279 idx_of(&states[..n], start)
280 .map(|i| is_af[i])
281 .unwrap_or(false)
282}
283
284pub fn all_until(graph: &[NQuin], start: u64, phi: u64, psi: u64, next: u64, holds: u64) -> bool {
288 let mut states = [0u64; MAX_CTL_STATES];
289 let n = reachable_states(graph, start, next, &mut states);
290 let mut au = [false; MAX_CTL_STATES];
291 for i in 0..n {
292 au[i] = satisfies(graph, states[i], holds, psi);
293 }
294 loop {
295 let mut changed = false;
296 for i in 0..n {
297 if au[i] || !satisfies(graph, states[i], holds, phi) {
298 continue;
299 }
300 let mut any_succ = false;
301 let mut all_au = true;
302 for q in graph {
303 if q.subject == states[i] && q.predicate == next {
304 any_succ = true;
305 match idx_of(&states[..n], q.object) {
306 Some(j) if au[j] => {}
307 _ => {
308 all_au = false;
309 break;
310 }
311 }
312 }
313 }
314 if any_succ && all_au {
315 au[i] = true;
316 changed = true;
317 }
318 }
319 if !changed {
320 break;
321 }
322 }
323 idx_of(&states[..n], start).map(|i| au[i]).unwrap_or(false)
324}
325
326fn alive_reaches_self(
328 graph: &[NQuin],
329 states: &[u64],
330 n: usize,
331 alive: &[bool],
332 start_idx: usize,
333 next: u64,
334) -> bool {
335 let target = states[start_idx];
336 let mut stack = [0usize; MAX_CTL_STATES];
337 let mut sl = 0usize;
338 let mut vis = [false; MAX_CTL_STATES];
339 for q in graph {
341 if q.subject == states[start_idx] && q.predicate == next {
342 if let Some(j) = idx_of(&states[..n], q.object) {
343 if alive[j] {
344 if states[j] == target {
345 return true;
346 }
347 if !vis[j] && sl < MAX_CTL_STATES {
348 vis[j] = true;
349 stack[sl] = j;
350 sl += 1;
351 }
352 }
353 }
354 }
355 }
356 while sl > 0 {
357 sl -= 1;
358 let cur = stack[sl];
359 for q in graph {
360 if q.subject == states[cur] && q.predicate == next {
361 if let Some(j) = idx_of(&states[..n], q.object) {
362 if alive[j] {
363 if states[j] == target {
364 return true;
365 }
366 if !vis[j] && sl < MAX_CTL_STATES {
367 vis[j] = true;
368 stack[sl] = j;
369 sl += 1;
370 }
371 }
372 }
373 }
374 }
375 }
376 false
377}
378
379pub fn fair_globally(
384 graph: &[NQuin],
385 start: u64,
386 prop: u64,
387 fair: u64,
388 next: u64,
389 holds: u64,
390) -> bool {
391 let mut states = [0u64; MAX_CTL_STATES];
392 let n = reachable_states(graph, start, next, &mut states);
393 let mut alive = [false; MAX_CTL_STATES];
394 for i in 0..n {
395 alive[i] = satisfies(graph, states[i], holds, prop);
396 }
397 loop {
398 let mut changed = false;
399 for i in 0..n {
400 if !alive[i] {
401 continue;
402 }
403 let mut has = false;
404 for q in graph {
405 if q.subject == states[i] && q.predicate == next {
406 if let Some(j) = idx_of(&states[..n], q.object) {
407 if alive[j] {
408 has = true;
409 break;
410 }
411 }
412 }
413 }
414 if !has {
415 alive[i] = false;
416 changed = true;
417 }
418 }
419 if !changed {
420 break;
421 }
422 }
423 for i in 0..n {
424 if alive[i]
425 && satisfies(graph, states[i], holds, fair)
426 && alive_reaches_self(graph, &states, n, &alive, i, next)
427 {
428 return true;
429 }
430 }
431 false
432}
433
434#[cfg(test)]
435mod tests {
436 use super::*;
437
438 fn t(from: u64, to: u64) -> NQuin {
439 let mut q = NQuin {
440 subject: from,
441 predicate: crate::q_hash("ctl:next"),
442 object: to,
443 context: 0,
444 metadata: 0,
445 parity: 0,
446 };
447 q.parity = q.subject ^ q.predicate ^ q.object ^ q.context;
448 q
449 }
450 fn label(state: u64, prop: u64) -> NQuin {
451 let mut q = NQuin {
452 subject: state,
453 predicate: crate::q_hash("ctl:holds"),
454 object: prop,
455 context: 0,
456 metadata: 0,
457 parity: 0,
458 };
459 q.parity = q.subject ^ q.predicate ^ q.object ^ q.context;
460 q
461 }
462
463 #[test]
464 fn ef_and_ag() {
465 let next = crate::q_hash("ctl:next");
466 let holds = crate::q_hash("ctl:holds");
467 let goal = 100u64;
468 let safe = 200u64;
469 let graph = [
471 t(1, 2),
472 t(2, 3),
473 label(3, goal),
474 label(1, safe),
475 label(2, safe),
476 label(3, safe),
477 ];
478 assert!(
479 exists_finally(&graph, 1, goal, next, holds),
480 "EF goal: state 3 is reachable"
481 );
482 assert!(
483 always_globally(&graph, 1, safe, next, holds),
484 "AG safe: all reachable states are safe"
485 );
486 let graph2 = [t(1, 2), t(2, 3), label(1, safe), label(3, safe)];
488 assert!(
489 !always_globally(&graph2, 1, safe, next, holds),
490 "AG fails when a reachable state lacks the invariant"
491 );
492 assert!(
493 !exists_finally(&graph2, 1, goal, next, holds),
494 "EF goal is false when no reachable state has it"
495 );
496 }
497
498 #[test]
499 fn ex_ax_eu_eg_af_operators() {
500 let next = crate::q_hash("ctl:next");
501 let holds = crate::q_hash("ctl:holds");
502 let (p, goal, a) = (100u64, 200u64, 50u64);
503
504 let g = [t(1, 2), t(1, 3), label(2, p)];
506 assert!(exists_next(&g, 1, p, next, holds), "EX: successor 2 has p");
507 assert!(
508 !always_next(&g, 1, p, next, holds),
509 "AX fails: successor 3 lacks p"
510 );
511 assert!(
512 always_next(&g, 2, p, next, holds),
513 "no successors → AX vacuously true"
514 );
515
516 let g2 = [t(1, 2), t(2, 3), label(1, a), label(2, a), label(3, goal)];
518 assert!(exists_until(&g2, 1, a, goal, next, holds));
519 let g3 = [t(1, 2), t(2, 3), label(1, a), label(3, goal)]; assert!(!exists_until(&g3, 1, a, goal, next, holds));
521 assert!(all_until(&g2, 1, a, goal, next, holds));
523 assert!(
524 !all_until(&g3, 1, a, goal, next, holds),
525 "a breaks before goal"
526 );
527
528 let g4 = [t(1, 2), t(2, 2), label(1, p), label(2, p)];
530 assert!(exists_globally(&g4, 1, p, next, holds));
531 let g5 = [t(1, 2), label(1, p)]; assert!(!exists_globally(&g5, 1, p, next, holds));
533
534 let g6 = [t(1, 2), t(2, 2), label(2, goal)];
536 assert!(all_finally(&g6, 1, goal, next, holds));
537 let g7 = [t(1, 2), t(2, 2), label(1, goal)]; assert!(
539 !all_finally(&g7, 2, goal, next, holds),
540 "a goal-free cycle never reaches goal"
541 );
542 }
543
544 #[test]
545 fn fair_eg_requires_a_fair_state_on_the_cycle() {
546 let next = crate::q_hash("ctl:next");
547 let holds = crate::q_hash("ctl:holds");
548 let (p, fair) = (100u64, 300u64);
549 let g = [t(1, 2), t(2, 2), label(1, p), label(2, p), label(2, fair)];
551 assert!(fair_globally(&g, 1, p, fair, next, holds));
552 let g2 = [t(1, 2), t(2, 2), label(1, p), label(2, p), label(1, fair)];
554 assert!(
555 !fair_globally(&g2, 1, p, fair, next, holds),
556 "the cycle has no fair state"
557 );
558 assert!(exists_globally(&g2, 1, p, next, holds));
560 }
561}