1#![allow(clippy::type_complexity)]
2#![allow(dead_code)]
12
13use std::collections::{HashMap, HashSet, VecDeque};
14
15#[derive(Debug, Clone, Copy, PartialEq)]
21pub struct IntervalDomain {
22 pub lo: f64,
24 pub hi: f64,
26}
27
28impl IntervalDomain {
29 pub fn new(lo: f64, hi: f64) -> Self {
33 debug_assert!(lo <= hi, "invalid interval: {} > {}", lo, hi);
34 Self { lo, hi }
35 }
36
37 pub fn top() -> Self {
39 Self {
40 lo: f64::NEG_INFINITY,
41 hi: f64::INFINITY,
42 }
43 }
44
45 pub fn bottom() -> Self {
47 Self {
48 lo: f64::INFINITY,
49 hi: f64::NEG_INFINITY,
50 }
51 }
52
53 pub fn is_non_empty(&self) -> bool {
55 self.lo <= self.hi
56 }
57
58 pub fn contains(&self, v: f64) -> bool {
60 v >= self.lo && v <= self.hi
61 }
62
63 pub fn join(&self, other: &Self) -> Self {
65 Self {
66 lo: self.lo.min(other.lo),
67 hi: self.hi.max(other.hi),
68 }
69 }
70
71 pub fn meet(&self, other: &Self) -> Self {
73 Self {
74 lo: self.lo.max(other.lo),
75 hi: self.hi.min(other.hi),
76 }
77 }
78
79 pub fn is_subset_of(&self, other: &Self) -> bool {
81 self.lo >= other.lo && self.hi <= other.hi
82 }
83
84 pub fn add(&self, other: &Self) -> Self {
86 Self::new(self.lo + other.lo, self.hi + other.hi)
87 }
88
89 pub fn sub(&self, other: &Self) -> Self {
91 Self::new(self.lo - other.hi, self.hi - other.lo)
92 }
93
94 pub fn mul(&self, other: &Self) -> Self {
96 let products = [
97 self.lo * other.lo,
98 self.lo * other.hi,
99 self.hi * other.lo,
100 self.hi * other.hi,
101 ];
102 Self::new(
103 products.iter().cloned().fold(f64::INFINITY, f64::min),
104 products.iter().cloned().fold(f64::NEG_INFINITY, f64::max),
105 )
106 }
107
108 pub fn div(&self, other: &Self) -> Self {
110 if other.lo <= 0.0 && other.hi >= 0.0 {
111 Self::top()
112 } else {
113 let quotients = [
114 self.lo / other.lo,
115 self.lo / other.hi,
116 self.hi / other.lo,
117 self.hi / other.hi,
118 ];
119 Self::new(
120 quotients.iter().cloned().fold(f64::INFINITY, f64::min),
121 quotients.iter().cloned().fold(f64::NEG_INFINITY, f64::max),
122 )
123 }
124 }
125
126 pub fn width(&self) -> f64 {
128 (self.hi - self.lo).max(0.0)
129 }
130
131 pub fn midpoint(&self) -> f64 {
133 (self.lo + self.hi) / 2.0
134 }
135}
136
137impl std::fmt::Display for IntervalDomain {
138 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
139 write!(f, "[{}, {}]", self.lo, self.hi)
140 }
141}
142
143#[derive(Debug, Clone)]
152pub struct LinearTemporalLogic {
153 pub formula: String,
155}
156
157impl LinearTemporalLogic {
158 pub fn new(formula: impl Into<String>) -> Self {
160 Self {
161 formula: formula.into(),
162 }
163 }
164
165 pub fn check_safety<S, F>(&self, trace: &[S], predicate: F) -> bool
171 where
172 F: Fn(&S) -> bool,
173 {
174 trace.iter().all(predicate)
175 }
176
177 pub fn check_liveness<S, F>(&self, trace: &[S], predicate: F) -> bool
182 where
183 F: Fn(&S) -> bool,
184 {
185 trace.iter().any(predicate)
186 }
187
188 pub fn check_next<S, F, G>(&self, trace: &[S], trigger: F, predicate: G) -> bool
191 where
192 F: Fn(&S) -> bool,
193 G: Fn(&S) -> bool,
194 {
195 for i in 0..trace.len().saturating_sub(1) {
196 if trigger(&trace[i]) && !predicate(&trace[i + 1]) {
197 return false;
198 }
199 }
200 true
201 }
202
203 pub fn check_until<S, F, G>(&self, trace: &[S], left: F, right: G) -> bool
205 where
206 F: Fn(&S) -> bool,
207 G: Fn(&S) -> bool,
208 {
209 let mut holding = false;
210 for s in trace {
211 if right(s) {
212 holding = true;
213 break;
214 }
215 if !left(s) {
216 return false;
217 }
218 }
219 holding
220 }
221}
222
223pub type StateIdx = usize;
229
230#[derive(Debug, Clone)]
235pub struct ModelChecker {
236 pub states: Vec<Vec<f64>>,
238 pub transitions: Vec<(StateIdx, StateIdx)>,
240}
241
242impl ModelChecker {
243 pub fn new(states: Vec<Vec<f64>>, transitions: Vec<(StateIdx, StateIdx)>) -> Self {
245 Self {
246 states,
247 transitions,
248 }
249 }
250
251 pub fn reachable_states(&self, start_idx: StateIdx) -> HashSet<StateIdx> {
253 let mut visited = HashSet::new();
254 let mut queue = VecDeque::new();
255 queue.push_back(start_idx);
256 visited.insert(start_idx);
257 while let Some(s) = queue.pop_front() {
258 for &(from, to) in &self.transitions {
259 if from == s && !visited.contains(&to) {
260 visited.insert(to);
261 queue.push_back(to);
262 }
263 }
264 }
265 visited
266 }
267
268 pub fn satisfies_invariant<F>(&self, start_idx: StateIdx, invariant: F) -> bool
270 where
271 F: Fn(&[f64]) -> bool,
272 {
273 let reachable = self.reachable_states(start_idx);
274 reachable.iter().all(|&s| invariant(&self.states[s]))
275 }
276
277 pub fn find_counterexample<F>(&self, start_idx: StateIdx, invariant: F) -> Option<StateIdx>
281 where
282 F: Fn(&[f64]) -> bool,
283 {
284 let reachable = self.reachable_states(start_idx);
285 reachable.into_iter().find(|&s| !invariant(&self.states[s]))
286 }
287
288 pub fn shortest_path(
291 &self,
292 start_idx: StateIdx,
293 target_idx: StateIdx,
294 ) -> Option<Vec<StateIdx>> {
295 let mut parent: HashMap<StateIdx, StateIdx> = HashMap::new();
296 let mut queue = VecDeque::new();
297 queue.push_back(start_idx);
298 parent.insert(start_idx, start_idx);
299 while let Some(s) = queue.pop_front() {
300 if s == target_idx {
301 let mut path = Vec::new();
303 let mut cur = target_idx;
304 loop {
305 path.push(cur);
306 let prev = parent[&cur];
307 if prev == cur {
308 break;
309 }
310 cur = prev;
311 }
312 path.reverse();
313 return Some(path);
314 }
315 for &(from, to) in &self.transitions {
316 if from == s && !parent.contains_key(&to) {
317 parent.insert(to, s);
318 queue.push_back(to);
319 }
320 }
321 }
322 None
323 }
324
325 pub fn reachable_count(&self, start_idx: StateIdx) -> usize {
327 self.reachable_states(start_idx).len()
328 }
329}
330
331#[derive(Debug, Clone)]
337pub struct AbstractInterpretation {
338 pub domain: HashMap<String, IntervalDomain>,
340}
341
342impl AbstractInterpretation {
343 pub fn new() -> Self {
345 Self {
346 domain: HashMap::new(),
347 }
348 }
349
350 pub fn bind(&mut self, var: impl Into<String>, value: f64) {
352 let name = var.into();
353 self.domain.insert(name, IntervalDomain::new(value, value));
354 }
355
356 pub fn bind_interval(&mut self, var: impl Into<String>, interval: IntervalDomain) {
358 self.domain.insert(var.into(), interval);
359 }
360
361 pub fn get(&self, var: &str) -> IntervalDomain {
363 self.domain
364 .get(var)
365 .copied()
366 .unwrap_or_else(IntervalDomain::top)
367 }
368
369 pub fn analyze_loop(
374 &mut self,
375 var: &str,
376 delta: IntervalDomain,
377 max_iter: usize,
378 ) -> IntervalDomain {
379 let mut current = self.get(var);
380 for _i in 0..max_iter {
381 let next = current.add(&delta);
382 let joined = current.join(&next);
383 let widened = self.widening(¤t, &joined);
384 if widened == current {
385 return current;
386 }
387 current = widened;
388 }
389 current
390 }
391
392 pub fn widening(&self, prev: &IntervalDomain, next: &IntervalDomain) -> IntervalDomain {
397 let lo = if next.lo < prev.lo {
398 f64::NEG_INFINITY
399 } else {
400 prev.lo
401 };
402 let hi = if next.hi > prev.hi {
403 f64::INFINITY
404 } else {
405 prev.hi
406 };
407 IntervalDomain { lo, hi }
408 }
409
410 pub fn narrowing(&self, prev: &IntervalDomain, next: &IntervalDomain) -> IntervalDomain {
413 let lo = if prev.lo == f64::NEG_INFINITY {
414 next.lo
415 } else {
416 prev.lo
417 };
418 let hi = if prev.hi == f64::INFINITY {
419 next.hi
420 } else {
421 prev.hi
422 };
423 IntervalDomain { lo, hi }
424 }
425
426 pub fn is_non_negative(&self, var: &str) -> bool {
428 self.get(var).lo >= 0.0
429 }
430
431 pub fn is_bounded(&self, var: &str) -> bool {
433 let iv = self.get(var);
434 iv.lo.is_finite() && iv.hi.is_finite()
435 }
436}
437
438impl Default for AbstractInterpretation {
439 fn default() -> Self {
440 Self::new()
441 }
442}
443
444#[derive(Debug, Clone)]
452pub struct LabeledTransitionSystem {
453 pub n_states: usize,
455 pub transitions: Vec<(usize, String, usize)>,
457 pub labels: Vec<Vec<String>>,
459}
460
461impl LabeledTransitionSystem {
462 pub fn new(n_states: usize) -> Self {
464 Self {
465 n_states,
466 transitions: Vec::new(),
467 labels: vec![Vec::new(); n_states],
468 }
469 }
470
471 pub fn add_transition(&mut self, from: usize, label: impl Into<String>, to: usize) {
473 self.transitions.push((from, label.into(), to));
474 }
475
476 pub fn add_label(&mut self, state: usize, label: impl Into<String>) {
478 if state < self.labels.len() {
479 self.labels[state].push(label.into());
480 }
481 }
482}
483
484#[derive(Debug)]
486pub struct BisimulationChecker {
487 pub lts: LabeledTransitionSystem,
489}
490
491impl BisimulationChecker {
492 pub fn new(lts: LabeledTransitionSystem) -> Self {
494 Self { lts }
495 }
496
497 pub fn are_bisimilar(&self, s: usize, t: usize) -> bool {
501 let partition = self.compute_quotient();
502 let s_class = partition.get(&s).copied().unwrap_or(s);
503 let t_class = partition.get(&t).copied().unwrap_or(t);
504 s_class == t_class
505 }
506
507 pub fn compute_quotient(&self) -> HashMap<usize, usize> {
512 let n = self.lts.n_states;
513 let mut partition: Vec<usize> = (0..n).map(|_| 0).collect();
515 let mut label_to_class: HashMap<Vec<String>, usize> = HashMap::new();
517 let mut next_class = 0usize;
518 for (i, labels) in self.lts.labels.iter().enumerate() {
519 let mut sorted_labels = labels.clone();
520 sorted_labels.sort();
521 let class = *label_to_class.entry(sorted_labels).or_insert_with(|| {
522 let c = next_class;
523 next_class += 1;
524 c
525 });
526 partition[i] = class;
527 }
528
529 loop {
531 let old_partition = partition.clone();
532 let mut signatures: Vec<(usize, Vec<(String, usize)>)> = Vec::new();
534 for i in 0..n {
535 let mut sig: Vec<(String, usize)> = self
536 .lts
537 .transitions
538 .iter()
539 .filter(|(from, _, _)| *from == i)
540 .map(|(_, label, to)| (label.clone(), partition[*to]))
541 .collect();
542 sig.sort();
543 signatures.push((partition[i], sig));
544 }
545 let mut sig_to_class: HashMap<(usize, Vec<(String, usize)>), usize> = HashMap::new();
547 next_class = 0;
548 for (i, sig) in signatures.iter().enumerate() {
549 let class = *sig_to_class.entry(sig.clone()).or_insert_with(|| {
550 let c = next_class;
551 next_class += 1;
552 c
553 });
554 partition[i] = class;
555 }
556 if partition == old_partition {
557 break;
558 }
559 }
560 partition.into_iter().enumerate().collect()
561 }
562
563 pub fn quotient_size(&self) -> usize {
565 let q = self.compute_quotient();
566 let classes: HashSet<usize> = q.values().copied().collect();
567 classes.len()
568 }
569}
570
571pub fn verify_energy_conservation(states: &[[f64; 6]], tol: f64) -> bool {
582 if states.len() < 2 {
583 return true;
584 }
585 let energies: Vec<f64> = states
586 .iter()
587 .map(|s| 0.5 * (s[3] * s[3] + s[4] * s[4] + s[5] * s[5]))
588 .collect();
589 let e0 = energies[0];
590 energies.iter().all(|&e| (e - e0).abs() <= tol)
591}
592
593pub fn verify_momentum_conservation(states: &[[f64; 6]], tol: f64) -> bool {
599 if states.len() < 2 {
600 return true;
601 }
602 let (px0, py0, pz0) = (states[0][3], states[0][4], states[0][5]);
603 states.iter().all(|s| {
604 (s[3] - px0).abs() <= tol && (s[4] - py0).abs() <= tol && (s[5] - pz0).abs() <= tol
605 })
606}
607
608pub fn verify_angular_momentum_conservation(states: &[[f64; 6]], tol: f64) -> bool {
613 if states.len() < 2 {
614 return true;
615 }
616 let lz0 = states[0][0] * states[0][4] - states[0][1] * states[0][3];
618 states
619 .iter()
620 .all(|s| ((s[0] * s[4] - s[1] * s[3]) - lz0).abs() <= tol)
621}
622
623#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
629pub struct Literal {
630 pub var: usize,
632 pub positive: bool,
634}
635
636impl Literal {
637 pub fn pos(var: usize) -> Self {
639 Self {
640 var,
641 positive: true,
642 }
643 }
644
645 pub fn neg(var: usize) -> Self {
647 Self {
648 var,
649 positive: false,
650 }
651 }
652
653 pub fn negate(&self) -> Self {
655 Self {
656 var: self.var,
657 positive: !self.positive,
658 }
659 }
660}
661
662#[derive(Debug, Clone)]
666pub struct SatisfiabilityChecker {
667 pub n_vars: usize,
669 pub clauses: Vec<Vec<Literal>>,
671}
672
673impl SatisfiabilityChecker {
674 pub fn new(n_vars: usize) -> Self {
676 Self {
677 n_vars,
678 clauses: Vec::new(),
679 }
680 }
681
682 pub fn add_clause(&mut self, clause: Vec<Literal>) {
684 self.clauses.push(clause);
685 }
686
687 pub fn solve(&self) -> Option<Vec<bool>> {
692 let mut assignment = vec![None::<bool>; self.n_vars];
693 if self.dpll(&mut assignment) {
694 Some(assignment.iter().map(|a| a.unwrap_or(true)).collect())
695 } else {
696 None
697 }
698 }
699
700 fn dpll(&self, assignment: &mut Vec<Option<bool>>) -> bool {
701 loop {
703 let mut propagated = false;
704 for clause in &self.clauses {
705 let (unassigned, satisfied) = self.clause_status(clause, assignment);
706 if satisfied {
707 continue;
708 }
709 if unassigned.is_empty() {
710 return false; }
712 if unassigned.len() == 1 {
713 let lit = unassigned[0];
715 assignment[lit.var] = Some(lit.positive);
716 propagated = true;
717 }
718 }
719 if !propagated {
720 break;
721 }
722 }
723
724 if self.all_satisfied(assignment) {
726 return true;
727 }
728 if self.has_conflict(assignment) {
730 return false;
731 }
732
733 if let Some(var) = (0..self.n_vars).find(|&v| assignment[v].is_none()) {
735 for &value in &[true, false] {
736 let mut child = assignment.clone();
737 child[var] = Some(value);
738 if self.dpll(&mut child) {
739 *assignment = child;
740 return true;
741 }
742 }
743 }
744 false
745 }
746
747 fn clause_status<'a>(
748 &self,
749 clause: &'a [Literal],
750 assignment: &[Option<bool>],
751 ) -> (Vec<&'a Literal>, bool) {
752 let mut unassigned = Vec::new();
753 for lit in clause {
754 match assignment[lit.var] {
755 Some(val) if val == lit.positive => return (vec![], true),
756 None => unassigned.push(lit),
757 _ => {}
758 }
759 }
760 (unassigned, false)
761 }
762
763 fn all_satisfied(&self, assignment: &[Option<bool>]) -> bool {
764 self.clauses.iter().all(|clause| {
765 clause
766 .iter()
767 .any(|lit| assignment[lit.var] == Some(lit.positive))
768 })
769 }
770
771 fn has_conflict(&self, assignment: &[Option<bool>]) -> bool {
772 self.clauses.iter().any(|clause| {
773 clause
774 .iter()
775 .all(|lit| assignment[lit.var].is_some_and(|v| v != lit.positive))
776 })
777 }
778
779 pub fn check_assignment(&self, assignment: &[bool]) -> bool {
781 self.clauses
782 .iter()
783 .all(|clause| clause.iter().any(|lit| assignment[lit.var] == lit.positive))
784 }
785
786 pub fn clause_count(&self) -> usize {
788 self.clauses.len()
789 }
790}
791
792pub fn check_lyapunov_stability(states: &[Vec<f64>]) -> bool {
801 if states.len() < 2 {
802 return true;
803 }
804 let norm = |s: &Vec<f64>| s.iter().map(|x| x * x).sum::<f64>().sqrt();
805 let first_norm = norm(&states[0]);
806 let last_norm = norm(states.last().expect("states has at least 2 entries"));
807 last_norm <= first_norm * 1.001 }
809
810pub struct Invariant {
816 pub name: String,
818 pub predicate: Box<dyn Fn(&[f64]) -> bool + Send + Sync>,
820}
821
822impl Invariant {
823 pub fn new(
825 name: impl Into<String>,
826 predicate: impl Fn(&[f64]) -> bool + Send + Sync + 'static,
827 ) -> Self {
828 Self {
829 name: name.into(),
830 predicate: Box::new(predicate),
831 }
832 }
833
834 pub fn check(&self, state: &[f64]) -> bool {
836 (self.predicate)(state)
837 }
838
839 pub fn verify_trajectory(&self, trajectory: &[Vec<f64>]) -> bool {
841 trajectory.iter().all(|s| self.check(s))
842 }
843}
844
845impl std::fmt::Debug for Invariant {
846 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
847 f.debug_struct("Invariant")
848 .field("name", &self.name)
849 .finish()
850 }
851}
852
853pub type SymbolicState = HashMap<String, IntervalDomain>;
859
860pub fn symbolic_step(
863 state: &SymbolicState,
864 var: &str,
865 delta: f64,
866 bounds: &IntervalDomain,
867) -> Option<SymbolicState> {
868 let iv = state.get(var).copied().unwrap_or_else(IntervalDomain::top);
869 let next_iv = IntervalDomain::new(iv.lo + delta, iv.hi + delta);
870 if next_iv.meet(bounds).is_non_empty() {
871 let mut next = state.clone();
872 next.insert(var.to_string(), next_iv.meet(bounds));
873 Some(next)
874 } else {
875 None
876 }
877}
878
879#[derive(Debug, Clone)]
885pub enum CegarResult {
886 Verified,
888 Counterexample(Vec<StateIdx>),
890 Unknown,
892}
893
894pub fn cegar_verify<F>(
898 checker: &ModelChecker,
899 start_idx: StateIdx,
900 invariant: F,
901 _max_iterations: usize,
902) -> CegarResult
903where
904 F: Fn(&[f64]) -> bool,
905{
906 match checker.find_counterexample(start_idx, &invariant) {
907 None => CegarResult::Verified,
908 Some(cex_state) => {
909 let path = checker
911 .shortest_path(start_idx, cex_state)
912 .unwrap_or_else(|| vec![cex_state]);
913 CegarResult::Counterexample(path)
914 }
915 }
916}
917
918pub fn verify_trace_properties(
926 trace: &[Vec<f64>],
927 properties: &[(&str, Box<dyn Fn(&[Vec<f64>]) -> bool>)],
928) -> HashMap<String, bool> {
929 properties
930 .iter()
931 .map(|(name, predicate)| (name.to_string(), predicate(trace)))
932 .collect()
933}
934
935#[derive(Debug, Clone)]
941pub struct TypeStateProtocol {
942 pub current_state: String,
944 pub transitions: Vec<(String, String, String)>,
946}
947
948impl TypeStateProtocol {
949 pub fn new(initial_state: impl Into<String>) -> Self {
951 Self {
952 current_state: initial_state.into(),
953 transitions: Vec::new(),
954 }
955 }
956
957 pub fn add_transition(
959 &mut self,
960 from: impl Into<String>,
961 event: impl Into<String>,
962 to: impl Into<String>,
963 ) {
964 self.transitions
965 .push((from.into(), event.into(), to.into()));
966 }
967
968 pub fn fire(&mut self, event: &str) -> Result<String, String> {
970 for (from, ev, to) in &self.transitions {
971 if from == &self.current_state && ev == event {
972 self.current_state = to.clone();
973 return Ok(to.clone());
974 }
975 }
976 Err(format!(
977 "no transition from '{}' on event '{}'",
978 self.current_state, event
979 ))
980 }
981
982 pub fn is_reachable(&self, initial: &str, target: &str) -> bool {
984 let mut visited = HashSet::new();
985 let mut queue = VecDeque::new();
986 queue.push_back(initial.to_string());
987 visited.insert(initial.to_string());
988 while let Some(s) = queue.pop_front() {
989 if s == target {
990 return true;
991 }
992 for (from, _, to) in &self.transitions {
993 if from == &s && !visited.contains(to) {
994 visited.insert(to.clone());
995 queue.push_back(to.clone());
996 }
997 }
998 }
999 false
1000 }
1001}
1002
1003pub fn always<S, F: Fn(&S) -> bool>(trace: &[S], predicate: F) -> bool {
1009 trace.iter().all(predicate)
1010}
1011
1012pub fn eventually<S, F: Fn(&S) -> bool>(trace: &[S], predicate: F) -> bool {
1014 trace.iter().any(predicate)
1015}
1016
1017pub fn globally_after<S, F: Fn(&S) -> bool, G: Fn(&S) -> bool>(
1020 trace: &[S],
1021 trigger: F,
1022 predicate: G,
1023) -> bool {
1024 let mut triggered = false;
1025 for s in trace {
1026 if triggered && !predicate(s) {
1027 return false;
1028 }
1029 if trigger(s) {
1030 triggered = true;
1031 }
1032 }
1033 true
1034}
1035
1036#[cfg(test)]
1041mod tests {
1042 use super::*;
1043
1044 #[test]
1047 fn test_interval_contains() {
1048 let iv = IntervalDomain::new(1.0, 3.0);
1049 assert!(iv.contains(2.0));
1050 assert!(!iv.contains(4.0));
1051 }
1052
1053 #[test]
1054 fn test_interval_join() {
1055 let a = IntervalDomain::new(1.0, 3.0);
1056 let b = IntervalDomain::new(2.0, 5.0);
1057 let j = a.join(&b);
1058 assert_eq!(j.lo, 1.0);
1059 assert_eq!(j.hi, 5.0);
1060 }
1061
1062 #[test]
1063 fn test_interval_meet() {
1064 let a = IntervalDomain::new(1.0, 4.0);
1065 let b = IntervalDomain::new(2.0, 6.0);
1066 let m = a.meet(&b);
1067 assert_eq!(m.lo, 2.0);
1068 assert_eq!(m.hi, 4.0);
1069 }
1070
1071 #[test]
1072 fn test_interval_add() {
1073 let a = IntervalDomain::new(1.0, 2.0);
1074 let b = IntervalDomain::new(3.0, 4.0);
1075 let c = a.add(&b);
1076 assert_eq!(c.lo, 4.0);
1077 assert_eq!(c.hi, 6.0);
1078 }
1079
1080 #[test]
1081 fn test_interval_sub() {
1082 let a = IntervalDomain::new(3.0, 5.0);
1083 let b = IntervalDomain::new(1.0, 2.0);
1084 let c = a.sub(&b);
1085 assert_eq!(c.lo, 1.0);
1086 assert_eq!(c.hi, 4.0);
1087 }
1088
1089 #[test]
1090 fn test_interval_mul() {
1091 let a = IntervalDomain::new(2.0, 3.0);
1092 let b = IntervalDomain::new(4.0, 5.0);
1093 let c = a.mul(&b);
1094 assert_eq!(c.lo, 8.0);
1095 assert_eq!(c.hi, 15.0);
1096 }
1097
1098 #[test]
1099 fn test_interval_div_no_zero() {
1100 let a = IntervalDomain::new(6.0, 8.0);
1101 let b = IntervalDomain::new(2.0, 4.0);
1102 let c = a.div(&b);
1103 assert!(c.lo >= 1.5 - 1e-9);
1104 assert!(c.hi <= 4.0 + 1e-9);
1105 }
1106
1107 #[test]
1108 fn test_interval_div_contains_zero() {
1109 let a = IntervalDomain::new(1.0, 2.0);
1110 let b = IntervalDomain::new(-1.0, 1.0);
1111 let c = a.div(&b);
1112 assert_eq!(c, IntervalDomain::top());
1113 }
1114
1115 #[test]
1116 fn test_interval_width() {
1117 let iv = IntervalDomain::new(2.0, 5.0);
1118 assert!((iv.width() - 3.0).abs() < 1e-10);
1119 }
1120
1121 #[test]
1124 fn test_ltl_check_safety() {
1125 let ltl = LinearTemporalLogic::new("always x >= 0");
1126 let trace = vec![1.0_f64, 2.0, 3.0];
1127 assert!(ltl.check_safety(&trace, |&x| x >= 0.0));
1128 let bad_trace = vec![1.0_f64, -1.0, 3.0];
1129 assert!(!ltl.check_safety(&bad_trace, |&x| x >= 0.0));
1130 }
1131
1132 #[test]
1133 fn test_ltl_check_liveness() {
1134 let ltl = LinearTemporalLogic::new("eventually x > 10");
1135 let trace = vec![1.0_f64, 5.0, 11.0];
1136 assert!(ltl.check_liveness(&trace, |&x| x > 10.0));
1137 let bad = vec![1.0_f64, 2.0, 3.0];
1138 assert!(!ltl.check_liveness(&bad, |&x| x > 10.0));
1139 }
1140
1141 #[test]
1142 fn test_ltl_check_next() {
1143 let ltl = LinearTemporalLogic::new("next");
1144 let trace = vec![true, true, false];
1145 assert!(!ltl.check_next(&trace, |&x| x, |&x| x));
1147 }
1148
1149 #[test]
1150 fn test_ltl_check_until() {
1151 let ltl = LinearTemporalLogic::new("until");
1152 let trace = vec![1i32, 1, 1, 2];
1153 assert!(ltl.check_until(&trace, |&x| x == 1, |&x| x == 2));
1154 let bad = vec![1i32, 0, 2];
1155 assert!(!ltl.check_until(&bad, |&x| x == 1, |&x| x == 2));
1156 }
1157
1158 fn simple_model() -> ModelChecker {
1161 ModelChecker::new(vec![vec![0.0], vec![1.0], vec![2.0]], vec![(0, 1), (1, 2)])
1163 }
1164
1165 #[test]
1166 fn test_model_reachable_states() {
1167 let mc = simple_model();
1168 let r = mc.reachable_states(0);
1169 assert!(r.contains(&0));
1170 assert!(r.contains(&1));
1171 assert!(r.contains(&2));
1172 }
1173
1174 #[test]
1175 fn test_model_satisfies_invariant() {
1176 let mc = simple_model();
1177 assert!(mc.satisfies_invariant(0, |s| s[0] >= 0.0));
1178 assert!(!mc.satisfies_invariant(0, |s| s[0] > 1.5));
1179 }
1180
1181 #[test]
1182 fn test_model_find_counterexample() {
1183 let mc = simple_model();
1184 let cex = mc.find_counterexample(0, |s| s[0] < 2.0);
1185 assert_eq!(cex, Some(2));
1186 }
1187
1188 #[test]
1189 fn test_model_shortest_path() {
1190 let mc = simple_model();
1191 let path = mc.shortest_path(0, 2).unwrap();
1192 assert_eq!(path, vec![0, 1, 2]);
1193 }
1194
1195 #[test]
1198 fn test_abstract_interp_bind_get() {
1199 let mut ai = AbstractInterpretation::new();
1200 ai.bind("x", 3.0);
1201 assert_eq!(ai.get("x"), IntervalDomain::new(3.0, 3.0));
1202 }
1203
1204 #[test]
1205 fn test_abstract_interp_widening() {
1206 let ai = AbstractInterpretation::new();
1207 let prev = IntervalDomain::new(0.0, 1.0);
1208 let next = IntervalDomain::new(0.0, 2.0);
1209 let w = ai.widening(&prev, &next);
1210 assert_eq!(w.hi, f64::INFINITY);
1211 }
1212
1213 #[test]
1214 fn test_abstract_interp_narrowing() {
1215 let ai = AbstractInterpretation::new();
1216 let prev = IntervalDomain::new(f64::NEG_INFINITY, f64::INFINITY);
1217 let next = IntervalDomain::new(-5.0, 5.0);
1218 let n = ai.narrowing(&prev, &next);
1219 assert_eq!(n.lo, -5.0);
1220 assert_eq!(n.hi, 5.0);
1221 }
1222
1223 #[test]
1224 fn test_abstract_interp_is_non_negative() {
1225 let mut ai = AbstractInterpretation::new();
1226 ai.bind_interval("x", IntervalDomain::new(0.0, 10.0));
1227 assert!(ai.is_non_negative("x"));
1228 ai.bind_interval("y", IntervalDomain::new(-1.0, 10.0));
1229 assert!(!ai.is_non_negative("y"));
1230 }
1231
1232 #[test]
1235 fn test_bisimulation_same_state() {
1236 let mut lts = LabeledTransitionSystem::new(2);
1237 lts.add_label(0, "a");
1238 lts.add_label(1, "a");
1239 lts.add_transition(0, "x", 0);
1240 lts.add_transition(1, "x", 1);
1241 let checker = BisimulationChecker::new(lts);
1242 assert!(checker.are_bisimilar(0, 1));
1243 }
1244
1245 #[test]
1246 fn test_bisimulation_different_labels() {
1247 let mut lts = LabeledTransitionSystem::new(2);
1248 lts.add_label(0, "a");
1249 lts.add_label(1, "b");
1250 let checker = BisimulationChecker::new(lts);
1251 assert!(!checker.are_bisimilar(0, 1));
1252 }
1253
1254 #[test]
1255 fn test_bisimulation_quotient_size() {
1256 let mut lts = LabeledTransitionSystem::new(3);
1257 lts.add_label(0, "a");
1258 lts.add_label(1, "a");
1259 lts.add_label(2, "b");
1260 let checker = BisimulationChecker::new(lts);
1261 assert_eq!(checker.quotient_size(), 2);
1262 }
1263
1264 #[test]
1267 fn test_verify_energy_conservation_ok() {
1268 let states = vec![[0.0, 0.0, 0.0, 1.0, 0.0, 0.0]; 5];
1269 assert!(verify_energy_conservation(&states, 1e-9));
1270 }
1271
1272 #[test]
1273 fn test_verify_energy_conservation_fail() {
1274 let s1 = [0.0, 0.0, 0.0, 1.0, 0.0, 0.0];
1275 let s2 = [0.0, 0.0, 0.0, 2.0, 0.0, 0.0];
1276 assert!(!verify_energy_conservation(&[s1, s2], 1e-9));
1277 }
1278
1279 #[test]
1280 fn test_verify_momentum_conservation_ok() {
1281 let states = vec![[0.0, 0.0, 0.0, 1.0, 2.0, 3.0]; 4];
1282 assert!(verify_momentum_conservation(&states, 1e-9));
1283 }
1284
1285 #[test]
1286 fn test_verify_momentum_conservation_fail() {
1287 let s1 = [0.0, 0.0, 0.0, 1.0, 0.0, 0.0];
1288 let s2 = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0];
1289 assert!(!verify_momentum_conservation(&[s1, s2], 1e-9));
1290 }
1291
1292 #[test]
1295 fn test_sat_trivial_sat() {
1296 let mut sat = SatisfiabilityChecker::new(1);
1298 sat.add_clause(vec![Literal::pos(0)]);
1299 let result = sat.solve();
1300 assert!(result.is_some());
1301 let assignment = result.unwrap();
1302 assert!(sat.check_assignment(&assignment));
1303 }
1304
1305 #[test]
1306 fn test_sat_trivial_unsat() {
1307 let mut sat = SatisfiabilityChecker::new(1);
1309 sat.add_clause(vec![Literal::pos(0)]);
1310 sat.add_clause(vec![Literal::neg(0)]);
1311 assert!(sat.solve().is_none());
1312 }
1313
1314 #[test]
1315 fn test_sat_two_vars() {
1316 let mut sat = SatisfiabilityChecker::new(2);
1318 sat.add_clause(vec![Literal::pos(0), Literal::pos(1)]);
1319 sat.add_clause(vec![Literal::neg(0), Literal::pos(1)]);
1320 let result = sat.solve();
1321 assert!(result.is_some());
1322 assert!(sat.check_assignment(&result.unwrap()));
1323 }
1324
1325 #[test]
1326 fn test_sat_check_assignment() {
1327 let mut sat = SatisfiabilityChecker::new(2);
1328 sat.add_clause(vec![Literal::pos(0), Literal::pos(1)]);
1329 assert!(sat.check_assignment(&[false, true]));
1330 assert!(!sat.check_assignment(&[false, false]));
1331 }
1332
1333 #[test]
1336 fn test_type_state_fire_ok() {
1337 let mut proto = TypeStateProtocol::new("idle");
1338 proto.add_transition("idle", "start", "running");
1339 proto.add_transition("running", "stop", "idle");
1340 assert!(proto.fire("start").is_ok());
1341 assert_eq!(proto.current_state, "running");
1342 }
1343
1344 #[test]
1345 fn test_type_state_fire_err() {
1346 let mut proto = TypeStateProtocol::new("idle");
1347 proto.add_transition("idle", "start", "running");
1348 assert!(proto.fire("stop").is_err());
1349 }
1350
1351 #[test]
1352 fn test_type_state_is_reachable() {
1353 let mut proto = TypeStateProtocol::new("idle");
1354 proto.add_transition("idle", "start", "running");
1355 proto.add_transition("running", "pause", "paused");
1356 assert!(proto.is_reachable("idle", "paused"));
1357 assert!(!proto.is_reachable("idle", "done"));
1358 }
1359
1360 #[test]
1363 fn test_always() {
1364 assert!(always(&[1, 2, 3], |&x| x > 0));
1365 assert!(!always(&[1, -1, 3], |&x| x > 0));
1366 }
1367
1368 #[test]
1369 fn test_eventually() {
1370 assert!(eventually(&[0, 0, 5], |&x| x > 4));
1371 assert!(!eventually(&[0, 0, 0], |&x| x > 4));
1372 }
1373
1374 #[test]
1375 fn test_globally_after() {
1376 let trace = vec![0i32, 1, 2, 3];
1378 assert!(globally_after(&trace, |&x| x == 1, |&x| x > 0));
1379 }
1380
1381 #[test]
1384 fn test_lyapunov_stable() {
1385 let trace: Vec<Vec<f64>> = (0..5).map(|i| vec![1.0 / (i as f64 + 1.0)]).collect();
1386 assert!(check_lyapunov_stability(&trace));
1387 }
1388
1389 #[test]
1392 fn test_cegar_verified() {
1393 let mc = simple_model();
1394 let result = cegar_verify(&mc, 0, |s| s[0] >= 0.0, 10);
1395 assert!(matches!(result, CegarResult::Verified));
1396 }
1397
1398 #[test]
1399 fn test_cegar_counterexample() {
1400 let mc = simple_model();
1401 let result = cegar_verify(&mc, 0, |s| s[0] < 2.0, 10);
1402 assert!(matches!(result, CegarResult::Counterexample(_)));
1403 }
1404
1405 #[test]
1408 fn test_symbolic_step_ok() {
1409 let mut state: SymbolicState = HashMap::new();
1410 state.insert("x".to_string(), IntervalDomain::new(0.0, 5.0));
1411 let bounds = IntervalDomain::new(0.0, 10.0);
1412 let next = symbolic_step(&state, "x", 1.0, &bounds);
1413 assert!(next.is_some());
1414 }
1415
1416 #[test]
1417 fn test_symbolic_step_out_of_bounds() {
1418 let mut state: SymbolicState = HashMap::new();
1419 state.insert("x".to_string(), IntervalDomain::new(9.0, 10.0));
1420 let bounds = IntervalDomain::new(0.0, 5.0);
1421 let next = symbolic_step(&state, "x", 3.0, &bounds);
1422 assert!(next.is_none());
1423 }
1424}