1use oxilean_kernel::{BinderInfo, Declaration, Environment, Expr, Level, Name};
6use std::collections::{HashMap, HashSet, VecDeque};
7
8use super::functions::*;
9
10#[derive(Debug, Clone)]
12pub struct CounterExample {
13 pub trace: Vec<usize>,
15 pub loop_start: Option<usize>,
17 pub violated_formula: String,
19}
20impl CounterExample {
21 pub fn finite(trace: Vec<usize>, formula: impl Into<String>) -> Self {
23 Self {
24 trace,
25 loop_start: None,
26 violated_formula: formula.into(),
27 }
28 }
29 pub fn lasso(trace: Vec<usize>, loop_start: usize, formula: impl Into<String>) -> Self {
31 Self {
32 trace,
33 loop_start: Some(loop_start),
34 violated_formula: formula.into(),
35 }
36 }
37 pub fn is_lasso(&self) -> bool {
39 self.loop_start.is_some()
40 }
41}
42#[derive(Debug, Clone)]
44pub struct LtlModelChecker {
45 pub kripke: KripkeStructure,
47}
48impl LtlModelChecker {
49 pub fn new(kripke: KripkeStructure) -> Self {
51 Self { kripke }
52 }
53 pub fn check_ltl(&self, formula: &LtlFormula) -> bool {
56 match formula {
57 LtlFormula::True_ => true,
58 LtlFormula::False_ => false,
59 LtlFormula::Always(inner) => {
60 if let LtlFormula::Atom(p) = inner.as_ref() {
61 let reachable = self.kripke.reachable_states();
62 reachable.iter().all(|&s| self.kripke.labeling[s].holds(p))
63 } else {
64 true
65 }
66 }
67 _ => true,
68 }
69 }
70 pub fn find_counterexample(&self, formula: &LtlFormula) -> Option<CounterExample> {
72 if !self.check_ltl(formula) {
73 let trace: Vec<usize> = self.kripke.reachable_states().into_iter().collect();
74 Some(CounterExample::finite(trace, format!("{}", formula)))
75 } else {
76 None
77 }
78 }
79 pub fn synthesize_strategy(&self, _formula: &LtlFormula) -> Option<String> {
81 None
82 }
83}
84#[derive(Debug, Clone)]
86pub struct AbstractTransformer {
87 pub name: String,
89}
90impl AbstractTransformer {
91 pub fn new(name: impl Into<String>) -> Self {
93 Self { name: name.into() }
94 }
95 pub fn apply(&self, domain: &AbstractDomain) -> AbstractDomain {
97 domain.clone()
98 }
99}
100#[derive(Debug, Clone)]
102pub struct KripkeStructure {
103 pub num_states: usize,
105 pub initial_states: Vec<usize>,
107 pub transition_relation: Vec<Vec<usize>>,
109 pub labeling: Vec<StateLabel>,
111}
112impl KripkeStructure {
113 pub fn new(n: usize) -> Self {
115 let labeling = (0..n).map(StateLabel::new).collect();
116 Self {
117 num_states: n,
118 initial_states: Vec::new(),
119 transition_relation: vec![Vec::new(); n],
120 labeling,
121 }
122 }
123 pub fn add_initial(&mut self, s: usize) {
125 if !self.initial_states.contains(&s) {
126 self.initial_states.push(s);
127 }
128 }
129 pub fn add_transition(&mut self, s: usize, t: usize) {
131 if s < self.num_states && t < self.num_states && !self.transition_relation[s].contains(&t) {
132 self.transition_relation[s].push(t);
133 }
134 }
135 pub fn label_state(&mut self, s: usize, prop: impl Into<String>) {
137 if s < self.num_states {
138 self.labeling[s].add(prop);
139 }
140 }
141 pub fn reachable_states(&self) -> HashSet<usize> {
143 let mut visited = HashSet::new();
144 let mut queue: VecDeque<usize> = self.initial_states.iter().copied().collect();
145 while let Some(s) = queue.pop_front() {
146 if visited.insert(s) {
147 for &t in &self.transition_relation[s] {
148 if !visited.contains(&t) {
149 queue.push_back(t);
150 }
151 }
152 }
153 }
154 visited
155 }
156 pub fn is_connected(&self) -> bool {
158 self.reachable_states().len() == self.num_states
159 }
160 pub fn compute_scc(&self) -> Vec<Vec<usize>> {
162 let n = self.num_states;
163 let mut visited = vec![false; n];
164 let mut finish_order = Vec::with_capacity(n);
165 for start in 0..n {
166 if !visited[start] {
167 self.dfs_finish(start, &mut visited, &mut finish_order);
168 }
169 }
170 let mut rev = vec![Vec::new(); n];
171 for s in 0..n {
172 for &t in &self.transition_relation[s] {
173 rev[t].push(s);
174 }
175 }
176 let mut visited2 = vec![false; n];
177 let mut sccs = Vec::new();
178 for &start in finish_order.iter().rev() {
179 if !visited2[start] {
180 let mut component = Vec::new();
181 Self::dfs_collect(start, &rev, &mut visited2, &mut component);
182 sccs.push(component);
183 }
184 }
185 sccs
186 }
187 fn dfs_finish(&self, v: usize, visited: &mut Vec<bool>, order: &mut Vec<usize>) {
188 visited[v] = true;
189 for &u in &self.transition_relation[v] {
190 if !visited[u] {
191 self.dfs_finish(u, visited, order);
192 }
193 }
194 order.push(v);
195 }
196 fn dfs_collect(
197 v: usize,
198 rev: &Vec<Vec<usize>>,
199 visited: &mut Vec<bool>,
200 comp: &mut Vec<usize>,
201 ) {
202 visited[v] = true;
203 comp.push(v);
204 for &u in &rev[v] {
205 if !visited[u] {
206 Self::dfs_collect(u, rev, visited, comp);
207 }
208 }
209 }
210}
211#[derive(Debug, Clone)]
213pub struct ParityGameZielonka {
214 pub num_vertices: usize,
216 pub priority: Vec<u32>,
218 pub owner: Vec<u8>,
220 pub successors: Vec<Vec<usize>>,
222}
223impl ParityGameZielonka {
224 pub fn new(n: usize) -> Self {
226 Self {
227 num_vertices: n,
228 priority: vec![0; n],
229 owner: vec![0; n],
230 successors: vec![Vec::new(); n],
231 }
232 }
233 pub fn set_priority(&mut self, v: usize, p: u32) {
235 if v < self.num_vertices {
236 self.priority[v] = p;
237 }
238 }
239 pub fn set_owner(&mut self, v: usize, player: u8) {
241 if v < self.num_vertices {
242 self.owner[v] = player & 1;
243 }
244 }
245 pub fn add_edge(&mut self, u: usize, v: usize) {
247 if u < self.num_vertices && v < self.num_vertices {
248 self.successors[u].push(v);
249 }
250 }
251 fn max_priority_in(&self, verts: &HashSet<usize>) -> u32 {
253 verts.iter().map(|&v| self.priority[v]).max().unwrap_or(0)
254 }
255 fn verts_with_priority(&self, verts: &HashSet<usize>, p: u32) -> HashSet<usize> {
257 verts
258 .iter()
259 .copied()
260 .filter(|&v| self.priority[v] == p)
261 .collect()
262 }
263 fn attractor(
265 &self,
266 player: u8,
267 target: &HashSet<usize>,
268 verts: &HashSet<usize>,
269 ) -> HashSet<usize> {
270 let mut attr = target.clone();
271 let mut queue: VecDeque<usize> = target.iter().copied().collect();
272 while let Some(v) = queue.pop_front() {
273 for u in verts {
274 if attr.contains(u) {
275 continue;
276 }
277 let succ_in_verts: Vec<usize> = self.successors[*u]
278 .iter()
279 .copied()
280 .filter(|&w| verts.contains(&w))
281 .collect();
282 if succ_in_verts.is_empty() {
283 continue;
284 }
285 let attracts = if self.owner[*u] == player {
286 succ_in_verts.iter().any(|w| attr.contains(w))
287 } else {
288 succ_in_verts.iter().all(|w| attr.contains(w))
289 };
290 if attracts && self.successors[*u].iter().any(|w| w == &v) {
291 attr.insert(*u);
292 queue.push_back(*u);
293 }
294 }
295 }
296 attr
297 }
298 pub fn solve(&self) -> (HashSet<usize>, HashSet<usize>) {
301 let all: HashSet<usize> = (0..self.num_vertices).collect();
302 self.zielonka(&all)
303 }
304 fn zielonka(&self, verts: &HashSet<usize>) -> (HashSet<usize>, HashSet<usize>) {
305 if verts.is_empty() {
306 return (HashSet::new(), HashSet::new());
307 }
308 let p = self.max_priority_in(verts);
309 let player = (p % 2) as u8;
310 let opponent = 1 - player;
311 let u = self.verts_with_priority(verts, p);
312 let attr_u = self.attractor(player, &u, verts);
313 let verts_minus: HashSet<usize> = verts.difference(&attr_u).copied().collect();
314 let (mut w0, mut w1) = self.zielonka(&verts_minus);
315 let (wo_player, wo_opp) = if player == 0 {
316 (&mut w0, &mut w1)
317 } else {
318 (&mut w1, &mut w0)
319 };
320 if wo_opp.is_empty() {
321 for &v in &attr_u {
322 wo_player.insert(v);
323 }
324 } else {
325 let attr_opp = self.attractor(opponent, wo_opp, verts);
326 let verts2: HashSet<usize> = verts.difference(&attr_opp).copied().collect();
327 let (mut w0b, mut w1b) = self.zielonka(&verts2);
328 if opponent == 0 {
329 for &v in &attr_opp {
330 w0b.insert(v);
331 }
332 } else {
333 for &v in &attr_opp {
334 w1b.insert(v);
335 }
336 }
337 if player == 0 {
338 return (w0b.clone(), w1b.clone());
339 } else {
340 return (w1b.clone(), w0b.clone());
341 }
342 }
343 (w0, w1)
344 }
345 pub fn player0_wins(&self, v: usize) -> bool {
347 let (w0, _) = self.solve();
348 w0.contains(&v)
349 }
350}
351#[derive(Debug, Clone, PartialEq, Eq)]
353pub enum CtlStarFormula {
354 Atom(String),
356 Not(Box<CtlStarFormula>),
358 And(Box<CtlStarFormula>, Box<CtlStarFormula>),
360 Or(Box<CtlStarFormula>, Box<CtlStarFormula>),
362 E(Box<CtlStarFormula>),
364 A(Box<CtlStarFormula>),
366 Next(Box<CtlStarFormula>),
368 Until(Box<CtlStarFormula>, Box<CtlStarFormula>),
370 Eventually(Box<CtlStarFormula>),
372 Always(Box<CtlStarFormula>),
374}
375#[derive(Debug, Clone)]
377pub struct CounterExampleGuidedRefinement {
378 pub domain: AbstractDomain,
380 pub iterations: usize,
382 pub verified: bool,
384}
385impl CounterExampleGuidedRefinement {
386 pub fn new(domain: AbstractDomain) -> Self {
388 Self {
389 domain,
390 iterations: 0,
391 verified: false,
392 }
393 }
394 pub fn abstract_states(&self, states: &[usize]) -> AbstractDomain {
396 let preds: Vec<String> = states.iter().map(|s| format!("s{}", s)).collect();
397 AbstractDomain::predicate(preds)
398 }
399 pub fn refine_abstraction(&mut self, spurious: &SpuriousCounterexample) {
401 self.domain
402 .predicates
403 .push(spurious.infeasibility_reason.clone());
404 self.iterations += 1;
405 }
406 pub fn check_feasibility(&self, cex: &CounterExample) -> bool {
408 !cex.trace.is_empty() && cex.loop_start.is_none()
409 }
410}
411#[derive(Debug, Clone)]
413pub struct SpuriousCounterexample {
414 pub abstract_trace: Vec<String>,
416 pub infeasibility_reason: String,
418}
419impl SpuriousCounterexample {
420 pub fn new(trace: Vec<String>, reason: impl Into<String>) -> Self {
422 Self {
423 abstract_trace: trace,
424 infeasibility_reason: reason.into(),
425 }
426 }
427}
428#[derive(Debug, Clone)]
430pub struct BDD {
431 pub nodes: Vec<BDDNode>,
433 pub root: usize,
435}
436impl BDD {
437 pub fn true_bdd() -> Self {
439 Self {
440 nodes: vec![BDDNode::Leaf(true)],
441 root: 0,
442 }
443 }
444 pub fn false_bdd() -> Self {
446 Self {
447 nodes: vec![BDDNode::Leaf(false)],
448 root: 0,
449 }
450 }
451 pub fn evaluate(&self, assignment: &HashMap<usize, bool>) -> bool {
453 let mut idx = self.root;
454 loop {
455 match &self.nodes[idx] {
456 BDDNode::Leaf(v) => return *v,
457 BDDNode::Node { var, low, high } => {
458 idx = if assignment.get(var).copied().unwrap_or(false) {
459 *high
460 } else {
461 *low
462 };
463 }
464 }
465 }
466 }
467}
468#[derive(Debug, Clone, PartialEq, Eq)]
470pub enum CtlFormula {
471 Atom(String),
473 True_,
475 False_,
477 Not(Box<CtlFormula>),
479 And(Box<CtlFormula>, Box<CtlFormula>),
481 Or(Box<CtlFormula>, Box<CtlFormula>),
483 EX(Box<CtlFormula>),
485 AX(Box<CtlFormula>),
487 EG(Box<CtlFormula>),
489 AG(Box<CtlFormula>),
491 EU(Box<CtlFormula>, Box<CtlFormula>),
493 AU(Box<CtlFormula>, Box<CtlFormula>),
495 EF(Box<CtlFormula>),
497 AF(Box<CtlFormula>),
499}
500impl CtlFormula {
501 pub fn negate(&self) -> Self {
503 match self {
504 CtlFormula::Not(f) => *f.clone(),
505 other => CtlFormula::Not(Box::new(other.clone())),
506 }
507 }
508 pub fn is_safety(&self) -> bool {
510 matches!(self, CtlFormula::AG(_))
511 }
512 pub fn is_liveness(&self) -> bool {
514 matches!(self, CtlFormula::AF(_))
515 }
516 pub fn is_fairness(&self) -> bool {
518 match self {
519 CtlFormula::AG(inner) => inner.is_liveness(),
520 _ => false,
521 }
522 }
523}
524#[derive(Debug, Clone, PartialEq, Eq)]
526pub enum LtlFormula {
527 Atom(String),
529 True_,
531 False_,
533 Not(Box<LtlFormula>),
535 And(Box<LtlFormula>, Box<LtlFormula>),
537 Or(Box<LtlFormula>, Box<LtlFormula>),
539 Next(Box<LtlFormula>),
541 Until(Box<LtlFormula>, Box<LtlFormula>),
543 Release(Box<LtlFormula>, Box<LtlFormula>),
545 Eventually(Box<LtlFormula>),
547 Always(Box<LtlFormula>),
549 WeakUntil(Box<LtlFormula>, Box<LtlFormula>),
551}
552impl LtlFormula {
553 pub fn atom(s: &str) -> Self {
555 LtlFormula::Atom(s.to_string())
556 }
557 pub fn negate(&self) -> Self {
559 match self {
560 LtlFormula::Not(f) => *f.clone(),
561 other => LtlFormula::Not(Box::new(other.clone())),
562 }
563 }
564 pub fn is_safety(&self) -> bool {
566 match self {
567 LtlFormula::Always(_) => true,
568 LtlFormula::And(a, b) => a.is_safety() && b.is_safety(),
569 _ => false,
570 }
571 }
572 pub fn is_liveness(&self) -> bool {
574 match self {
575 LtlFormula::Eventually(_) => true,
576 LtlFormula::Or(a, b) => a.is_liveness() || b.is_liveness(),
577 _ => false,
578 }
579 }
580 pub fn is_fairness(&self) -> bool {
582 match self {
583 LtlFormula::Always(inner) => inner.is_liveness(),
584 _ => false,
585 }
586 }
587}
588#[derive(Debug, Clone)]
590pub struct StateLabel {
591 pub state: usize,
593 pub propositions: HashSet<String>,
595}
596impl StateLabel {
597 pub fn new(state: usize) -> Self {
599 Self {
600 state,
601 propositions: HashSet::new(),
602 }
603 }
604 pub fn add(&mut self, prop: impl Into<String>) {
606 self.propositions.insert(prop.into());
607 }
608 pub fn holds(&self, prop: &str) -> bool {
610 self.propositions.contains(prop)
611 }
612}
613#[derive(Debug, Clone)]
615pub struct MuCalculusEvaluator {
616 pub kripke: KripkeStructure,
618 pub max_iter: usize,
620}
621impl MuCalculusEvaluator {
622 pub fn new(kripke: KripkeStructure) -> Self {
624 Self {
625 kripke,
626 max_iter: 1000,
627 }
628 }
629 pub fn eval(
631 &self,
632 formula: &MuFormula,
633 env: &mut HashMap<String, HashSet<usize>>,
634 ) -> HashSet<usize> {
635 match formula {
636 MuFormula::True_ => (0..self.kripke.num_states).collect(),
637 MuFormula::False_ => HashSet::new(),
638 MuFormula::Prop(p) => (0..self.kripke.num_states)
639 .filter(|&s| self.kripke.labeling[s].holds(p))
640 .collect(),
641 MuFormula::Var(x) => env.get(x).cloned().unwrap_or_default(),
642 MuFormula::Neg(f) => {
643 let all: HashSet<usize> = (0..self.kripke.num_states).collect();
644 let sf = self.eval(f, env);
645 all.difference(&sf).copied().collect()
646 }
647 MuFormula::And(a, b) => {
648 let sa = self.eval(a, env);
649 let sb = self.eval(b, env);
650 sa.intersection(&sb).copied().collect()
651 }
652 MuFormula::Or(a, b) => {
653 let sa = self.eval(a, env);
654 let sb = self.eval(b, env);
655 sa.union(&sb).copied().collect()
656 }
657 MuFormula::Diamond(f) => {
658 let sf = self.eval(f, env);
659 (0..self.kripke.num_states)
660 .filter(|&s| {
661 self.kripke.transition_relation[s]
662 .iter()
663 .any(|t| sf.contains(t))
664 })
665 .collect()
666 }
667 MuFormula::Box_(f) => {
668 let sf = self.eval(f, env);
669 (0..self.kripke.num_states)
670 .filter(|&s| {
671 self.kripke.transition_relation[s]
672 .iter()
673 .all(|t| sf.contains(t))
674 })
675 .collect()
676 }
677 MuFormula::Mu(x, f) => {
678 let mut t: HashSet<usize> = HashSet::new();
679 for _ in 0..self.max_iter {
680 env.insert(x.clone(), t.clone());
681 let new_t = self.eval(f, env);
682 if new_t == t {
683 env.remove(x);
684 return t;
685 }
686 t = new_t;
687 }
688 env.remove(x);
689 t
690 }
691 MuFormula::Nu(x, f) => {
692 let mut t: HashSet<usize> = (0..self.kripke.num_states).collect();
693 for _ in 0..self.max_iter {
694 env.insert(x.clone(), t.clone());
695 let new_t = self.eval(f, env);
696 if new_t == t {
697 env.remove(x);
698 return t;
699 }
700 t = new_t;
701 }
702 env.remove(x);
703 t
704 }
705 }
706 }
707 pub fn check(&self, formula: &MuFormula) -> bool {
709 let mut env = HashMap::new();
710 let sat = self.eval(formula, &mut env);
711 self.kripke.initial_states.iter().all(|s| sat.contains(s))
712 }
713}
714#[derive(Debug, Clone)]
716pub struct BDDModelChecker {
717 pub mgr: BDDManager,
719 pub num_vars: usize,
721 pub init_bdd: usize,
723 pub trans_bdd: usize,
725}
726impl BDDModelChecker {
727 pub fn new(num_vars: usize) -> Self {
729 let mgr = BDDManager::new();
730 let init_bdd = mgr.true_node();
731 let trans_bdd = mgr.false_node();
732 Self {
733 mgr,
734 num_vars,
735 init_bdd,
736 trans_bdd,
737 }
738 }
739 pub fn set_init(&mut self, bdd: usize) {
741 self.init_bdd = bdd;
742 }
743 pub fn set_trans(&mut self, bdd: usize) {
745 self.trans_bdd = bdd;
746 }
747 pub fn post(&mut self, states: usize) -> usize {
749 let combined = self.mgr.bdd_and(states, self.trans_bdd);
750 let mut result = combined;
751 for v in 0..self.num_vars {
752 result = self.mgr.bdd_quantify_exists(result, v);
753 }
754 result
755 }
756 pub fn pre(&mut self, states: usize) -> usize {
758 let combined = self.mgr.bdd_and(self.trans_bdd, states);
759 let mut result = combined;
760 for v in self.num_vars..2 * self.num_vars {
761 result = self.mgr.bdd_quantify_exists(result, v);
762 }
763 result
764 }
765 pub fn reachable(&mut self) -> usize {
767 let mut reach = self.init_bdd;
768 loop {
769 let next_states = self.post(reach);
770 let new_reach = self.mgr.bdd_or(reach, next_states);
771 if new_reach == reach {
772 break;
773 }
774 reach = new_reach;
775 }
776 reach
777 }
778 pub fn check_ag_safe(&mut self, safe: usize) -> bool {
780 let reach = self.reachable();
781 let false_node = self.mgr.false_node();
782 let true_node = self.mgr.true_node();
783 let not_safe = if safe == true_node {
784 false_node
785 } else if safe == false_node {
786 true_node
787 } else {
788 let intersection = self.mgr.bdd_and(reach, safe);
789 return intersection == reach;
790 };
791 let bad = self.mgr.bdd_and(reach, not_safe);
792 bad == false_node
793 }
794 pub fn check_ef(&mut self, target: usize) -> bool {
796 let reach = self.reachable();
797 let witness = self.mgr.bdd_and(reach, target);
798 let false_node = self.mgr.false_node();
799 witness != false_node
800 }
801}
802#[derive(Debug, Clone)]
804pub struct BDDManager {
805 pub unique_table: HashMap<BDDNode, usize>,
807 pub nodes: Vec<BDDNode>,
809 pub apply_cache: HashMap<(u8, usize, usize), usize>,
811}
812impl BDDManager {
813 pub fn new() -> Self {
815 let mut mgr = Self {
816 unique_table: HashMap::new(),
817 nodes: Vec::new(),
818 apply_cache: HashMap::new(),
819 };
820 mgr.get_or_create(BDDNode::Leaf(false));
821 mgr.get_or_create(BDDNode::Leaf(true));
822 mgr
823 }
824 fn get_or_create(&mut self, node: BDDNode) -> usize {
825 if let Some(&id) = self.unique_table.get(&node) {
826 return id;
827 }
828 let id = self.nodes.len();
829 self.nodes.push(node.clone());
830 self.unique_table.insert(node, id);
831 id
832 }
833 pub fn false_node(&self) -> usize {
835 0
836 }
837 pub fn true_node(&self) -> usize {
839 1
840 }
841 pub fn var(&mut self, var: usize) -> usize {
843 self.get_or_create(BDDNode::Node {
844 var,
845 low: 0,
846 high: 1,
847 })
848 }
849 pub fn bdd_and(&mut self, a: usize, b: usize) -> usize {
851 if a == self.false_node() || b == self.false_node() {
852 return self.false_node();
853 }
854 if a == self.true_node() {
855 return b;
856 }
857 if b == self.true_node() {
858 return a;
859 }
860 if a == b {
861 return a;
862 }
863 let key = (0u8, a, b);
864 if let Some(&r) = self.apply_cache.get(&key) {
865 return r;
866 }
867 let result = match (self.nodes[a].clone(), self.nodes[b].clone()) {
868 (
869 BDDNode::Node {
870 var: va,
871 low: la,
872 high: ha,
873 },
874 BDDNode::Node {
875 var: vb,
876 low: lb,
877 high: hb,
878 },
879 ) => {
880 let (var, low_a, high_a, low_b, high_b) = if va == vb {
881 (va, la, ha, lb, hb)
882 } else if va < vb {
883 (va, la, ha, b, b)
884 } else {
885 (vb, a, a, lb, hb)
886 };
887 let low = self.bdd_and(low_a, low_b);
888 let high = self.bdd_and(high_a, high_b);
889 if low == high {
890 low
891 } else {
892 self.get_or_create(BDDNode::Node { var, low, high })
893 }
894 }
895 _ => self.false_node(),
896 };
897 self.apply_cache.insert(key, result);
898 result
899 }
900 pub fn bdd_or(&mut self, a: usize, b: usize) -> usize {
902 if a == self.true_node() || b == self.true_node() {
903 return self.true_node();
904 }
905 if a == self.false_node() {
906 return b;
907 }
908 if b == self.false_node() {
909 return a;
910 }
911 if a == b {
912 return a;
913 }
914 let key = (1u8, a, b);
915 if let Some(&r) = self.apply_cache.get(&key) {
916 return r;
917 }
918 let result = match (self.nodes[a].clone(), self.nodes[b].clone()) {
919 (
920 BDDNode::Node {
921 var: va,
922 low: la,
923 high: ha,
924 },
925 BDDNode::Node {
926 var: vb,
927 low: lb,
928 high: hb,
929 },
930 ) => {
931 let (var, low_a, high_a, low_b, high_b) = if va == vb {
932 (va, la, ha, lb, hb)
933 } else if va < vb {
934 (va, la, ha, b, b)
935 } else {
936 (vb, a, a, lb, hb)
937 };
938 let low = self.bdd_or(low_a, low_b);
939 let high = self.bdd_or(high_a, high_b);
940 if low == high {
941 low
942 } else {
943 self.get_or_create(BDDNode::Node { var, low, high })
944 }
945 }
946 _ => self.true_node(),
947 };
948 self.apply_cache.insert(key, result);
949 result
950 }
951 pub fn bdd_quantify_exists(&mut self, a: usize, var: usize) -> usize {
953 match self.nodes[a].clone() {
954 BDDNode::Leaf(_) => a,
955 BDDNode::Node { var: v, low, high } => {
956 if v == var {
957 self.bdd_or(low, high)
958 } else {
959 let new_low = self.bdd_quantify_exists(low, var);
960 let new_high = self.bdd_quantify_exists(high, var);
961 if new_low == new_high {
962 new_low
963 } else {
964 self.get_or_create(BDDNode::Node {
965 var: v,
966 low: new_low,
967 high: new_high,
968 })
969 }
970 }
971 }
972 }
973 }
974}
975#[derive(Debug, Clone)]
978pub struct ProbabilisticMCVerifier {
979 pub num_states: usize,
981 pub transitions: Vec<Vec<(usize, f64)>>,
984 pub labeling: Vec<HashSet<String>>,
986 pub initial: Vec<(usize, f64)>,
988}
989impl ProbabilisticMCVerifier {
990 pub fn new(n: usize) -> Self {
992 Self {
993 num_states: n,
994 transitions: vec![Vec::new(); n],
995 labeling: vec![HashSet::new(); n],
996 initial: Vec::new(),
997 }
998 }
999 pub fn add_transition(&mut self, s: usize, t: usize, p: f64) {
1001 if s < self.num_states && t < self.num_states {
1002 self.transitions[s].push((t, p));
1003 }
1004 }
1005 pub fn label_state(&mut self, s: usize, prop: impl Into<String>) {
1007 if s < self.num_states {
1008 self.labeling[s].insert(prop.into());
1009 }
1010 }
1011 pub fn set_initial(&mut self, s: usize, p: f64) {
1013 self.initial.push((s, p));
1014 }
1015 pub fn reachability_prob(&self, target: &HashSet<usize>) -> Vec<f64> {
1018 let mut prob = vec![0.0f64; self.num_states];
1019 for &s in target {
1020 if s < self.num_states {
1021 prob[s] = 1.0;
1022 }
1023 }
1024 for _ in 0..500 {
1025 let mut new_prob = prob.clone();
1026 for s in 0..self.num_states {
1027 if target.contains(&s) {
1028 new_prob[s] = 1.0;
1029 continue;
1030 }
1031 new_prob[s] = self.transitions[s].iter().map(|&(t, p)| p * prob[t]).sum();
1032 }
1033 let diff: f64 = prob
1034 .iter()
1035 .zip(new_prob.iter())
1036 .map(|(a, b)| (a - b).abs())
1037 .fold(0.0f64, f64::max);
1038 prob = new_prob;
1039 if diff < 1e-10 {
1040 break;
1041 }
1042 }
1043 prob
1044 }
1045 pub fn check_prob_reach(&self, target_prop: &str, threshold: f64) -> bool {
1048 let target: HashSet<usize> = (0..self.num_states)
1049 .filter(|&s| self.labeling[s].contains(target_prop))
1050 .collect();
1051 let prob = self.reachability_prob(&target);
1052 self.initial
1053 .iter()
1054 .all(|&(s, _w)| prob[s] >= threshold - 1e-9)
1055 }
1056 pub fn expected_steps_to_reach(&self, s: usize, target: &HashSet<usize>) -> f64 {
1058 if target.contains(&s) {
1059 return 0.0;
1060 }
1061 let prob = self.reachability_prob(target);
1062 if prob[s] < 1e-12 {
1063 f64::INFINITY
1064 } else {
1065 1.0 / prob[s]
1066 }
1067 }
1068}
1069#[derive(Debug, Clone)]
1071pub struct CtlModelChecker {
1072 pub kripke: KripkeStructure,
1074}
1075impl CtlModelChecker {
1076 pub fn new(kripke: KripkeStructure) -> Self {
1078 Self { kripke }
1079 }
1080 pub fn sat_ex(&self, phi_states: &HashSet<usize>) -> HashSet<usize> {
1082 let mut result = HashSet::new();
1083 for s in 0..self.kripke.num_states {
1084 if self.kripke.transition_relation[s]
1085 .iter()
1086 .any(|t| phi_states.contains(t))
1087 {
1088 result.insert(s);
1089 }
1090 }
1091 result
1092 }
1093 pub fn sat_eu(
1095 &self,
1096 phi_states: &HashSet<usize>,
1097 psi_states: &HashSet<usize>,
1098 ) -> HashSet<usize> {
1099 let mut t = psi_states.clone();
1100 loop {
1101 let ex_t = self.sat_ex(&t);
1102 let new_t: HashSet<usize> = t
1103 .iter()
1104 .chain(ex_t.iter().filter(|s| phi_states.contains(s)))
1105 .copied()
1106 .collect();
1107 if new_t == t {
1108 break;
1109 }
1110 t = new_t;
1111 }
1112 t
1113 }
1114 pub fn sat_eg(&self, phi_states: &HashSet<usize>) -> HashSet<usize> {
1116 let mut t = phi_states.clone();
1117 loop {
1118 let ex_t = self.sat_ex(&t);
1119 let new_t: HashSet<usize> = t.iter().filter(|s| ex_t.contains(s)).copied().collect();
1120 if new_t == t {
1121 break;
1122 }
1123 t = new_t;
1124 }
1125 t
1126 }
1127 pub fn sat(&self, formula: &CtlFormula) -> HashSet<usize> {
1129 match formula {
1130 CtlFormula::True_ => (0..self.kripke.num_states).collect(),
1131 CtlFormula::False_ => HashSet::new(),
1132 CtlFormula::Atom(p) => (0..self.kripke.num_states)
1133 .filter(|&s| self.kripke.labeling[s].holds(p))
1134 .collect(),
1135 CtlFormula::Not(f) => {
1136 let all: HashSet<usize> = (0..self.kripke.num_states).collect();
1137 let sat_f = self.sat(f);
1138 all.difference(&sat_f).copied().collect()
1139 }
1140 CtlFormula::And(a, b) => {
1141 let sa = self.sat(a);
1142 let sb = self.sat(b);
1143 sa.intersection(&sb).copied().collect()
1144 }
1145 CtlFormula::Or(a, b) => {
1146 let sa = self.sat(a);
1147 let sb = self.sat(b);
1148 sa.union(&sb).copied().collect()
1149 }
1150 CtlFormula::EX(f) => {
1151 let sf = self.sat(f);
1152 self.sat_ex(&sf)
1153 }
1154 CtlFormula::AX(f) => {
1155 let not_f = CtlFormula::Not(f.clone());
1156 let ex_not_f = self.sat_ex(&self.sat(¬_f));
1157 let all: HashSet<usize> = (0..self.kripke.num_states).collect();
1158 all.difference(&ex_not_f).copied().collect()
1159 }
1160 CtlFormula::EG(f) => {
1161 let sf = self.sat(f);
1162 self.sat_eg(&sf)
1163 }
1164 CtlFormula::AG(f) => {
1165 let not_phi = CtlFormula::Not(f.clone());
1166 let true_states: HashSet<usize> = (0..self.kripke.num_states).collect();
1167 let ef_not = self.sat_eu(&true_states, &self.sat(¬_phi));
1168 let all: HashSet<usize> = (0..self.kripke.num_states).collect();
1169 all.difference(&ef_not).copied().collect()
1170 }
1171 CtlFormula::EU(a, b) => {
1172 let sa = self.sat(a);
1173 let sb = self.sat(b);
1174 self.sat_eu(&sa, &sb)
1175 }
1176 CtlFormula::AU(a, b) => {
1177 let not_psi = CtlFormula::Not(b.clone());
1178 let not_phi = CtlFormula::Not(a.clone());
1179 let s_not_psi = self.sat(¬_psi);
1180 let s_not_phi = self.sat(¬_phi);
1181 let eg_not_psi = self.sat_eg(&s_not_psi);
1182 let both_neg: HashSet<usize> =
1183 s_not_phi.intersection(&s_not_psi).copied().collect();
1184 let eu_part = self.sat_eu(&s_not_psi, &both_neg);
1185 let bad: HashSet<usize> = eg_not_psi.union(&eu_part).copied().collect();
1186 let all: HashSet<usize> = (0..self.kripke.num_states).collect();
1187 all.difference(&bad).copied().collect()
1188 }
1189 CtlFormula::EF(f) => {
1190 let all_states: HashSet<usize> = (0..self.kripke.num_states).collect();
1191 let sf = self.sat(f);
1192 self.sat_eu(&all_states, &sf)
1193 }
1194 CtlFormula::AF(f) => {
1195 let not_phi = CtlFormula::Not(f.clone());
1196 let s_not_phi = self.sat(¬_phi);
1197 let eg = self.sat_eg(&s_not_phi);
1198 let all: HashSet<usize> = (0..self.kripke.num_states).collect();
1199 all.difference(&eg).copied().collect()
1200 }
1201 }
1202 }
1203 pub fn check_ctl(&self, formula: &CtlFormula) -> bool {
1205 let sat = self.sat(formula);
1206 self.kripke.initial_states.iter().all(|s| sat.contains(s))
1207 }
1208 pub fn find_counterexample(&self, formula: &CtlFormula) -> Option<CounterExample> {
1210 let sat = self.sat(formula);
1211 let bad: Vec<usize> = self
1212 .kripke
1213 .initial_states
1214 .iter()
1215 .filter(|s| !sat.contains(s))
1216 .copied()
1217 .collect();
1218 if bad.is_empty() {
1219 None
1220 } else {
1221 Some(CounterExample::finite(bad, format!("{:?}", formula)))
1222 }
1223 }
1224}
1225#[derive(Debug, Clone)]
1227pub struct SymbolicTransitionRelation {
1228 pub bdd_id: usize,
1230 pub num_vars: usize,
1232}
1233impl SymbolicTransitionRelation {
1234 pub fn new(bdd_id: usize, num_vars: usize) -> Self {
1236 Self { bdd_id, num_vars }
1237 }
1238 pub fn image(&self, mgr: &mut BDDManager, states: usize) -> usize {
1240 let combined = mgr.bdd_and(states, self.bdd_id);
1241 let mut result = combined;
1242 for v in 0..self.num_vars {
1243 result = mgr.bdd_quantify_exists(result, v);
1244 }
1245 result
1246 }
1247 pub fn pre_image(&self, mgr: &mut BDDManager, states: usize) -> usize {
1249 let combined = mgr.bdd_and(self.bdd_id, states);
1250 let mut result = combined;
1251 for v in self.num_vars..2 * self.num_vars {
1252 result = mgr.bdd_quantify_exists(result, v);
1253 }
1254 result
1255 }
1256}
1257#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1259pub enum BDDNode {
1260 Leaf(bool),
1262 Node { var: usize, low: usize, high: usize },
1264}
1265#[derive(Debug, Clone, PartialEq, Eq)]
1267pub enum MuFormula {
1268 Prop(String),
1270 True_,
1272 False_,
1274 Neg(Box<MuFormula>),
1276 And(Box<MuFormula>, Box<MuFormula>),
1278 Or(Box<MuFormula>, Box<MuFormula>),
1280 Diamond(Box<MuFormula>),
1282 Box_(Box<MuFormula>),
1284 Mu(String, Box<MuFormula>),
1286 Nu(String, Box<MuFormula>),
1288 Var(String),
1290}
1291#[derive(Debug, Clone)]
1293pub struct BuchiAutomaton {
1294 pub num_states: usize,
1296 pub alphabet: Vec<String>,
1298 pub transitions: Vec<Vec<(HashSet<String>, usize)>>,
1300 pub initial_state: usize,
1302 pub accepting_states: HashSet<usize>,
1304}
1305impl BuchiAutomaton {
1306 pub fn new(n: usize) -> Self {
1308 Self {
1309 num_states: n,
1310 alphabet: Vec::new(),
1311 transitions: vec![Vec::new(); n],
1312 initial_state: 0,
1313 accepting_states: HashSet::new(),
1314 }
1315 }
1316 pub fn add_accepting(&mut self, q: usize) {
1318 self.accepting_states.insert(q);
1319 }
1320 pub fn add_transition(&mut self, q: usize, label: HashSet<String>, r: usize) {
1322 if q < self.num_states {
1323 self.transitions[q].push((label, r));
1324 }
1325 }
1326 pub fn has_accepting_states(&self) -> bool {
1328 !self.accepting_states.is_empty()
1329 }
1330}
1331#[derive(Debug, Clone)]
1333pub struct AbstractDomain {
1334 pub kind: String,
1336 pub predicates: Vec<String>,
1338}
1339impl AbstractDomain {
1340 pub fn predicate(predicates: Vec<String>) -> Self {
1342 Self {
1343 kind: "predicate".into(),
1344 predicates,
1345 }
1346 }
1347 pub fn interval() -> Self {
1349 Self {
1350 kind: "interval".into(),
1351 predicates: Vec::new(),
1352 }
1353 }
1354 pub fn is_top(&self) -> bool {
1356 self.predicates.is_empty()
1357 }
1358}
1359#[derive(Debug, Clone, PartialEq, Eq, Hash)]
1361pub struct AtomicProposition {
1362 pub name: String,
1364}
1365impl AtomicProposition {
1366 pub fn new(name: impl Into<String>) -> Self {
1368 Self { name: name.into() }
1369 }
1370}