1use super::TLExpr;
11use std::collections::HashSet;
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
15pub enum TemporalClass {
16 Safety,
18 Liveness,
20 Fairness,
22 Persistence,
24 Recurrence,
26 Mixed,
28}
29
30#[derive(Debug, Clone, PartialEq, Eq, Hash)]
32pub enum TemporalPattern {
33 AlwaysP,
35 EventuallyP,
37 EventuallyAlwaysP,
39 AlwaysEventuallyP,
41 PUntilQ,
43 AlwaysImpliesEventually,
45 AlwaysImpliesNext,
47 Complex,
49}
50
51#[derive(Debug, Clone, Default)]
53pub struct TemporalComplexity {
54 pub temporal_depth: usize,
56 pub temporal_op_count: usize,
58 pub until_count: usize,
60 pub release_count: usize,
62 pub next_count: usize,
64 pub has_fairness: bool,
66}
67
68pub fn classify_temporal_formula(expr: &TLExpr) -> TemporalClass {
73 match expr {
74 TLExpr::Eventually(e) if matches!(**e, TLExpr::Always(_)) => TemporalClass::Persistence,
76
77 TLExpr::Eventually(_) => TemporalClass::Liveness,
79
80 TLExpr::Always(e) if matches!(**e, TLExpr::Eventually(_)) => TemporalClass::Recurrence,
82
83 TLExpr::Always(e) if matches!(&**e, TLExpr::Imply(_, then_branch) if matches!(**then_branch, TLExpr::Eventually(_))) => {
85 TemporalClass::Fairness
86 }
87
88 TLExpr::Always(_) => TemporalClass::Safety,
90
91 TLExpr::And(left, right) => {
93 let left_class = classify_temporal_formula(left);
94 let right_class = classify_temporal_formula(right);
95 if left_class == right_class {
96 left_class
97 } else {
98 TemporalClass::Mixed
99 }
100 }
101
102 _ => TemporalClass::Mixed,
103 }
104}
105
106pub fn identify_temporal_pattern(expr: &TLExpr) -> TemporalPattern {
108 match expr {
109 TLExpr::Always(e) if !is_temporal(e) => TemporalPattern::AlwaysP,
110
111 TLExpr::Eventually(e) if !is_temporal(e) => TemporalPattern::EventuallyP,
112
113 TLExpr::Eventually(e) if matches!(**e, TLExpr::Always(_)) => {
114 TemporalPattern::EventuallyAlwaysP
115 }
116
117 TLExpr::Always(e) if matches!(**e, TLExpr::Eventually(_)) => {
118 TemporalPattern::AlwaysEventuallyP
119 }
120
121 TLExpr::Until { .. } => TemporalPattern::PUntilQ,
122
123 TLExpr::Always(e) => {
124 if let TLExpr::Imply(_, then_branch) = &**e {
125 if matches!(**then_branch, TLExpr::Eventually(_)) {
126 return TemporalPattern::AlwaysImpliesEventually;
127 } else if matches!(**then_branch, TLExpr::Next(_)) {
128 return TemporalPattern::AlwaysImpliesNext;
129 }
130 }
131 TemporalPattern::Complex
132 }
133
134 _ => TemporalPattern::Complex,
135 }
136}
137
138pub fn is_temporal(expr: &TLExpr) -> bool {
140 match expr {
141 TLExpr::Next(_)
142 | TLExpr::Eventually(_)
143 | TLExpr::Always(_)
144 | TLExpr::Until { .. }
145 | TLExpr::Release { .. }
146 | TLExpr::WeakUntil { .. }
147 | TLExpr::StrongRelease { .. } => true,
148
149 TLExpr::And(l, r) | TLExpr::Or(l, r) | TLExpr::Imply(l, r) => {
150 is_temporal(l) || is_temporal(r)
151 }
152
153 TLExpr::Not(e) | TLExpr::Score(e) => is_temporal(e),
154
155 TLExpr::Exists { body, .. }
156 | TLExpr::ForAll { body, .. }
157 | TLExpr::SoftExists { body, .. }
158 | TLExpr::SoftForAll { body, .. } => is_temporal(body),
159
160 TLExpr::IfThenElse {
161 condition,
162 then_branch,
163 else_branch,
164 } => is_temporal(condition) || is_temporal(then_branch) || is_temporal(else_branch),
165
166 _ => false,
167 }
168}
169
170pub fn compute_temporal_complexity(expr: &TLExpr) -> TemporalComplexity {
172 fn compute_rec(expr: &TLExpr, depth: usize) -> TemporalComplexity {
173 let mut metrics = TemporalComplexity {
174 temporal_depth: depth,
175 ..Default::default()
176 };
177
178 match expr {
179 TLExpr::Next(e) => {
180 metrics.next_count = 1;
181 metrics.temporal_op_count = 1;
182 let inner = compute_rec(e, depth + 1);
183 metrics.merge(inner);
184 }
185
186 TLExpr::Eventually(e) => {
187 metrics.temporal_op_count = 1;
188 let inner = compute_rec(e, depth + 1);
189 metrics.merge(inner);
190 }
191
192 TLExpr::Always(e) => {
193 metrics.temporal_op_count = 1;
194 if let TLExpr::Imply(_, then_br) = &**e {
196 if matches!(**then_br, TLExpr::Eventually(_)) {
197 metrics.has_fairness = true;
198 }
199 }
200 let inner = compute_rec(e, depth + 1);
201 metrics.merge(inner);
202 }
203
204 TLExpr::Until { before, after } => {
205 metrics.until_count = 1;
206 metrics.temporal_op_count = 1;
207 let before_metrics = compute_rec(before, depth + 1);
208 let after_metrics = compute_rec(after, depth + 1);
209 metrics.merge(before_metrics);
210 metrics.merge(after_metrics);
211 }
212
213 TLExpr::Release { released, releaser } => {
214 metrics.release_count = 1;
215 metrics.temporal_op_count = 1;
216 let released_metrics = compute_rec(released, depth + 1);
217 let releaser_metrics = compute_rec(releaser, depth + 1);
218 metrics.merge(released_metrics);
219 metrics.merge(releaser_metrics);
220 }
221
222 TLExpr::WeakUntil { before, after } => {
223 metrics.until_count = 1;
224 metrics.temporal_op_count = 1;
225 let before_metrics = compute_rec(before, depth);
226 let after_metrics = compute_rec(after, depth);
227 metrics.merge(before_metrics);
228 metrics.merge(after_metrics);
229 }
230
231 TLExpr::StrongRelease { released, releaser } => {
232 metrics.release_count = 1;
233 metrics.temporal_op_count = 1;
234 let released_metrics = compute_rec(released, depth);
235 let releaser_metrics = compute_rec(releaser, depth);
236 metrics.merge(released_metrics);
237 metrics.merge(releaser_metrics);
238 }
239
240 TLExpr::And(l, r) | TLExpr::Or(l, r) | TLExpr::Imply(l, r) => {
241 let left_metrics = compute_rec(l, depth);
242 let right_metrics = compute_rec(r, depth);
243 metrics.merge(left_metrics);
244 metrics.merge(right_metrics);
245 }
246
247 TLExpr::Not(e) | TLExpr::Score(e) => {
248 let inner = compute_rec(e, depth);
249 metrics.merge(inner);
250 }
251
252 _ => {}
253 }
254
255 metrics
256 }
257
258 compute_rec(expr, 0)
259}
260
261impl TemporalComplexity {
262 fn merge(&mut self, other: TemporalComplexity) {
263 self.temporal_depth = self.temporal_depth.max(other.temporal_depth);
264 self.temporal_op_count += other.temporal_op_count;
265 self.until_count += other.until_count;
266 self.release_count += other.release_count;
267 self.next_count += other.next_count;
268 self.has_fairness = self.has_fairness || other.has_fairness;
269 }
270}
271
272pub fn extract_temporal_subformulas(expr: &TLExpr) -> Vec<TLExpr> {
274 let mut result = Vec::new();
275 extract_temporal_rec(expr, &mut result);
276 result
277}
278
279fn extract_temporal_rec(expr: &TLExpr, result: &mut Vec<TLExpr>) {
280 match expr {
281 TLExpr::Next(_)
282 | TLExpr::Eventually(_)
283 | TLExpr::Always(_)
284 | TLExpr::Until { .. }
285 | TLExpr::Release { .. }
286 | TLExpr::WeakUntil { .. }
287 | TLExpr::StrongRelease { .. } => {
288 result.push(expr.clone());
289 }
290 _ => {}
291 }
292
293 match expr {
295 TLExpr::And(l, r) | TLExpr::Or(l, r) | TLExpr::Imply(l, r) => {
296 extract_temporal_rec(l, result);
297 extract_temporal_rec(r, result);
298 }
299 TLExpr::Not(e)
300 | TLExpr::Score(e)
301 | TLExpr::Next(e)
302 | TLExpr::Eventually(e)
303 | TLExpr::Always(e) => {
304 extract_temporal_rec(e, result);
305 }
306 TLExpr::Until { before, after }
307 | TLExpr::Release {
308 released: before,
309 releaser: after,
310 }
311 | TLExpr::WeakUntil { before, after }
312 | TLExpr::StrongRelease {
313 released: before,
314 releaser: after,
315 } => {
316 extract_temporal_rec(before, result);
317 extract_temporal_rec(after, result);
318 }
319 _ => {}
320 }
321}
322
323pub fn decompose_safety_liveness(expr: &TLExpr) -> (Option<TLExpr>, Option<TLExpr>) {
328 match expr {
329 TLExpr::Always(e) if !has_liveness(e) => (Some(expr.clone()), None),
331
332 TLExpr::Eventually(e) if !has_safety(e) => (None, Some(expr.clone())),
334
335 TLExpr::And(left, right) => {
337 let (left_safety, left_liveness) = decompose_safety_liveness(left);
338 let (right_safety, right_liveness) = decompose_safety_liveness(right);
339
340 let safety = match (left_safety, right_safety) {
341 (Some(l), Some(r)) => Some(TLExpr::and(l, r)),
342 (Some(l), None) => Some(l),
343 (None, Some(r)) => Some(r),
344 (None, None) => None,
345 };
346
347 let liveness = match (left_liveness, right_liveness) {
348 (Some(l), Some(r)) => Some(TLExpr::and(l, r)),
349 (Some(l), None) => Some(l),
350 (None, Some(r)) => Some(r),
351 (None, None) => None,
352 };
353
354 (safety, liveness)
355 }
356
357 _ => (Some(expr.clone()), None),
359 }
360}
361
362fn has_liveness(expr: &TLExpr) -> bool {
364 match expr {
365 TLExpr::Eventually(_) => true,
366 TLExpr::Always(e) if matches!(**e, TLExpr::Eventually(_)) => true,
367 TLExpr::Until { .. } => true,
368 TLExpr::And(l, r) | TLExpr::Or(l, r) => has_liveness(l) || has_liveness(r),
369 TLExpr::Not(e) => has_liveness(e),
370 _ => false,
371 }
372}
373
374fn has_safety(expr: &TLExpr) -> bool {
376 match expr {
377 TLExpr::Always(e) if !matches!(**e, TLExpr::Eventually(_)) => true,
378 TLExpr::And(l, r) | TLExpr::Or(l, r) => has_safety(l) || has_safety(r),
379 TLExpr::Not(e) => has_safety(e),
380 _ => false,
381 }
382}
383
384pub fn is_temporal_nnf(expr: &TLExpr) -> bool {
388 match expr {
389 TLExpr::Not(e) => {
390 matches!(**e, TLExpr::Pred { .. } | TLExpr::Constant(_))
392 }
393
394 TLExpr::And(l, r) | TLExpr::Or(l, r) | TLExpr::Imply(l, r) => {
395 is_temporal_nnf(l) && is_temporal_nnf(r)
396 }
397
398 TLExpr::Next(e) | TLExpr::Eventually(e) | TLExpr::Always(e) => is_temporal_nnf(e),
399
400 TLExpr::Until { before, after }
401 | TLExpr::Release {
402 released: before,
403 releaser: after,
404 }
405 | TLExpr::WeakUntil { before, after }
406 | TLExpr::StrongRelease {
407 released: before,
408 releaser: after,
409 } => is_temporal_nnf(before) && is_temporal_nnf(after),
410
411 _ => true,
412 }
413}
414
415pub fn apply_advanced_ltl_equivalences(expr: &TLExpr) -> TLExpr {
422 match expr {
423 TLExpr::Eventually(e) => {
425 if let TLExpr::Or(left, right) = &**e {
426 return TLExpr::or(
427 TLExpr::eventually(apply_advanced_ltl_equivalences(left)),
428 TLExpr::eventually(apply_advanced_ltl_equivalences(right)),
429 );
430 }
431 TLExpr::eventually(apply_advanced_ltl_equivalences(e))
432 }
433
434 TLExpr::Always(e) => {
436 if let TLExpr::And(left, right) = &**e {
437 return TLExpr::and(
438 TLExpr::always(apply_advanced_ltl_equivalences(left)),
439 TLExpr::always(apply_advanced_ltl_equivalences(right)),
440 );
441 }
442 TLExpr::always(apply_advanced_ltl_equivalences(e))
443 }
444
445 TLExpr::And(left, right) => {
447 let is_gfp_left =
449 matches!(left.as_ref(), TLExpr::Always(e) if matches!(**e, TLExpr::Eventually(_)));
450 let is_fgp_right =
451 matches!(right.as_ref(), TLExpr::Eventually(e) if matches!(**e, TLExpr::Always(_)));
452
453 if is_gfp_left && is_fgp_right {
454 return apply_advanced_ltl_equivalences(right);
455 }
456
457 let is_fgp_left =
459 matches!(left.as_ref(), TLExpr::Eventually(e) if matches!(**e, TLExpr::Always(_)));
460 let is_gfp_right =
461 matches!(right.as_ref(), TLExpr::Always(e) if matches!(**e, TLExpr::Eventually(_)));
462
463 if is_fgp_left && is_gfp_right {
464 return apply_advanced_ltl_equivalences(left);
465 }
466
467 TLExpr::and(
468 apply_advanced_ltl_equivalences(left),
469 apply_advanced_ltl_equivalences(right),
470 )
471 }
472
473 TLExpr::Or(l, r) => TLExpr::or(
475 apply_advanced_ltl_equivalences(l),
476 apply_advanced_ltl_equivalences(r),
477 ),
478
479 TLExpr::Imply(l, r) => TLExpr::imply(
480 apply_advanced_ltl_equivalences(l),
481 apply_advanced_ltl_equivalences(r),
482 ),
483
484 TLExpr::Not(e) => TLExpr::negate(apply_advanced_ltl_equivalences(e)),
485
486 TLExpr::Next(e) => TLExpr::next(apply_advanced_ltl_equivalences(e)),
487
488 TLExpr::Until { before, after } => TLExpr::until(
489 apply_advanced_ltl_equivalences(before),
490 apply_advanced_ltl_equivalences(after),
491 ),
492
493 _ => expr.clone(),
494 }
495}
496
497pub fn extract_state_predicates(expr: &TLExpr) -> HashSet<String> {
502 let mut predicates = HashSet::new();
503 extract_state_predicates_rec(expr, &mut predicates);
504 predicates
505}
506
507fn extract_state_predicates_rec(expr: &TLExpr, predicates: &mut HashSet<String>) {
508 match expr {
509 TLExpr::Pred { name, .. } => {
510 predicates.insert(name.clone());
511 }
512
513 TLExpr::And(l, r) | TLExpr::Or(l, r) | TLExpr::Imply(l, r) => {
514 extract_state_predicates_rec(l, predicates);
515 extract_state_predicates_rec(r, predicates);
516 }
517
518 TLExpr::Not(e)
519 | TLExpr::Score(e)
520 | TLExpr::Next(e)
521 | TLExpr::Eventually(e)
522 | TLExpr::Always(e) => {
523 extract_state_predicates_rec(e, predicates);
524 }
525
526 TLExpr::Until { before, after }
527 | TLExpr::Release {
528 released: before,
529 releaser: after,
530 } => {
531 extract_state_predicates_rec(before, predicates);
532 extract_state_predicates_rec(after, predicates);
533 }
534
535 _ => {}
536 }
537}
538
539#[cfg(test)]
540mod tests {
541 use super::*;
542 use crate::Term;
543
544 #[test]
545 fn test_classify_safety_property() {
546 let expr = TLExpr::always(TLExpr::pred("safe", vec![Term::var("x")]));
547 assert_eq!(classify_temporal_formula(&expr), TemporalClass::Safety);
548 }
549
550 #[test]
551 fn test_classify_liveness_property() {
552 let expr = TLExpr::eventually(TLExpr::pred("goal", vec![Term::var("x")]));
553 assert_eq!(classify_temporal_formula(&expr), TemporalClass::Liveness);
554 }
555
556 #[test]
557 fn test_classify_persistence() {
558 let expr = TLExpr::eventually(TLExpr::always(TLExpr::pred("stable", vec![])));
559 assert_eq!(classify_temporal_formula(&expr), TemporalClass::Persistence);
560 }
561
562 #[test]
563 fn test_classify_fairness() {
564 let request = TLExpr::pred("request", vec![]);
565 let grant = TLExpr::pred("grant", vec![]);
566 let expr = TLExpr::always(TLExpr::imply(request, TLExpr::eventually(grant)));
567 assert_eq!(classify_temporal_formula(&expr), TemporalClass::Fairness);
568 }
569
570 #[test]
571 fn test_identify_pattern_always() {
572 let expr = TLExpr::always(TLExpr::pred("P", vec![]));
573 assert_eq!(identify_temporal_pattern(&expr), TemporalPattern::AlwaysP);
574 }
575
576 #[test]
577 fn test_identify_pattern_eventually_always() {
578 let expr = TLExpr::eventually(TLExpr::always(TLExpr::pred("P", vec![])));
579 assert_eq!(
580 identify_temporal_pattern(&expr),
581 TemporalPattern::EventuallyAlwaysP
582 );
583 }
584
585 #[test]
586 fn test_is_temporal() {
587 let temporal = TLExpr::next(TLExpr::pred("P", vec![]));
588 assert!(is_temporal(&temporal));
589
590 let non_temporal = TLExpr::pred("P", vec![]);
591 assert!(!is_temporal(&non_temporal));
592 }
593
594 #[test]
595 fn test_temporal_complexity() {
596 let expr = TLExpr::until(
597 TLExpr::pred("P", vec![]),
598 TLExpr::eventually(TLExpr::pred("Q", vec![])),
599 );
600
601 let metrics = compute_temporal_complexity(&expr);
602 assert_eq!(metrics.until_count, 1);
603 assert_eq!(metrics.temporal_op_count, 2);
604 assert!(metrics.temporal_depth >= 1);
605 }
606
607 #[test]
608 fn test_extract_temporal_subformulas() {
609 let expr = TLExpr::and(
610 TLExpr::always(TLExpr::pred("P", vec![])),
611 TLExpr::eventually(TLExpr::pred("Q", vec![])),
612 );
613
614 let subformulas = extract_temporal_subformulas(&expr);
615 assert_eq!(subformulas.len(), 2);
616 }
617
618 #[test]
619 fn test_decompose_pure_safety() {
620 let expr = TLExpr::always(TLExpr::pred("safe", vec![]));
621 let (safety, liveness) = decompose_safety_liveness(&expr);
622 assert!(safety.is_some());
623 assert!(liveness.is_none());
624 }
625
626 #[test]
627 fn test_decompose_pure_liveness() {
628 let expr = TLExpr::eventually(TLExpr::pred("goal", vec![]));
629 let (safety, liveness) = decompose_safety_liveness(&expr);
630 assert!(safety.is_none());
631 assert!(liveness.is_some());
632 }
633
634 #[test]
635 fn test_is_temporal_nnf() {
636 let nnf = TLExpr::and(
638 TLExpr::pred("P", vec![]),
639 TLExpr::negate(TLExpr::pred("Q", vec![])),
640 );
641 assert!(is_temporal_nnf(&nnf));
642
643 let not_nnf = TLExpr::negate(TLExpr::and(
645 TLExpr::pred("P", vec![]),
646 TLExpr::pred("Q", vec![]),
647 ));
648 assert!(!is_temporal_nnf(¬_nnf));
649 }
650
651 #[test]
652 fn test_distributive_eventually() {
653 let expr = TLExpr::eventually(TLExpr::or(
654 TLExpr::pred("P", vec![]),
655 TLExpr::pred("Q", vec![]),
656 ));
657
658 let result = apply_advanced_ltl_equivalences(&expr);
659 assert!(matches!(result, TLExpr::Or(_, _)));
661 }
662
663 #[test]
664 fn test_distributive_always() {
665 let expr = TLExpr::always(TLExpr::and(
666 TLExpr::pred("P", vec![]),
667 TLExpr::pred("Q", vec![]),
668 ));
669
670 let result = apply_advanced_ltl_equivalences(&expr);
671 assert!(matches!(result, TLExpr::And(_, _)));
673 }
674
675 #[test]
676 fn test_extract_state_predicates() {
677 let expr = TLExpr::and(
678 TLExpr::eventually(TLExpr::pred("P", vec![])),
679 TLExpr::always(TLExpr::pred("Q", vec![])),
680 );
681
682 let predicates = extract_state_predicates(&expr);
683 assert_eq!(predicates.len(), 2);
684 assert!(predicates.contains("P"));
685 assert!(predicates.contains("Q"));
686 }
687
688 #[test]
689 fn test_fairness_detection() {
690 let request = TLExpr::pred("req", vec![]);
691 let grant = TLExpr::pred("grant", vec![]);
692 let fairness = TLExpr::always(TLExpr::imply(request, TLExpr::eventually(grant)));
693
694 let metrics = compute_temporal_complexity(&fairness);
695 assert!(metrics.has_fairness);
696 }
697}