1use super::types::{
4 BisimulationRelation, CoalgebraMap, Codata, CoinductiveProof, GreibachNormalForm, LazyStream,
5 StreamApprox, StreamNode,
6};
7
8impl<T: Clone> LazyStream<T> {
11 pub fn new(prefix: Vec<T>, cycle: Vec<T>) -> Self {
18 LazyStream { prefix, cycle }
19 }
20
21 pub fn constant(val: T) -> Self {
23 LazyStream {
24 prefix: Vec::new(),
25 cycle: vec![val],
26 }
27 }
28
29 pub fn cycle(vals: Vec<T>) -> Self {
33 assert!(!vals.is_empty(), "cycle must be non-empty");
34 LazyStream {
35 prefix: Vec::new(),
36 cycle: vals,
37 }
38 }
39
40 pub fn take(&self, n: usize) -> Vec<T> {
42 (0..n).map(|i| self.nth(i)).collect()
43 }
44
45 pub fn nth(&self, n: usize) -> T {
47 if n < self.prefix.len() {
48 self.prefix[n].clone()
49 } else if self.cycle.is_empty() {
50 self.prefix
52 .last()
53 .cloned()
54 .expect("LazyStream: both prefix and cycle are empty")
55 } else {
56 let cycle_idx = (n - self.prefix.len()) % self.cycle.len();
57 self.cycle[cycle_idx].clone()
58 }
59 }
60
61 pub fn map<U: Clone, F: Fn(&T) -> U>(&self, f: F) -> LazyStream<U> {
63 LazyStream {
64 prefix: self.prefix.iter().map(|x| f(x)).collect(),
65 cycle: self.cycle.iter().map(|x| f(x)).collect(),
66 }
67 }
68
69 pub fn zip<U: Clone, V: Clone, F: Fn(&T, &U) -> V>(
75 &self,
76 other: &LazyStream<U>,
77 f: F,
78 ) -> LazyStream<V> {
79 let lcm_len = if self.cycle.is_empty() || other.cycle.is_empty() {
81 0
82 } else {
83 lcm(self.cycle.len(), other.cycle.len())
84 };
85
86 let prefix_len = self.prefix.len().max(other.prefix.len());
88 let combined_prefix: Vec<V> = (0..prefix_len)
89 .map(|i| f(&self.nth(i), &other.nth(i)))
90 .collect();
91
92 let combined_cycle: Vec<V> = (0..lcm_len)
93 .map(|i| f(&self.nth(prefix_len + i), &other.nth(prefix_len + i)))
94 .collect();
95
96 LazyStream {
97 prefix: combined_prefix,
98 cycle: combined_cycle,
99 }
100 }
101
102 pub fn to_approx(&self, n: usize) -> StreamApprox<T> {
104 self.to_approx_at(0, n)
105 }
106
107 fn to_approx_at(&self, offset: usize, depth: usize) -> StreamApprox<T> {
108 if depth == 0 {
109 StreamApprox::Nil
110 } else {
111 let head = self.nth(offset);
112 StreamApprox::Cons(Box::new(StreamNode {
113 head,
114 tail: Box::new(self.to_approx_at(offset + 1, depth - 1)),
115 }))
116 }
117 }
118}
119
120fn gcd(a: usize, b: usize) -> usize {
123 if b == 0 {
124 a
125 } else {
126 gcd(b, a % b)
127 }
128}
129
130fn lcm(a: usize, b: usize) -> usize {
131 if a == 0 || b == 0 {
132 0
133 } else {
134 a / gcd(a, b) * b
135 }
136}
137
138impl<S: Clone + Eq, O: Clone + Eq> CoalgebraMap<S, O> {
141 pub fn new() -> Self {
143 CoalgebraMap {
144 transitions: Vec::new(),
145 }
146 }
147
148 pub fn add_transition(&mut self, src: S, obs: O, dst: S) {
150 self.transitions.push((src, obs, dst));
151 }
152
153 pub fn transitions_from(&self, state: &S) -> Vec<(&O, &S)> {
155 self.transitions
156 .iter()
157 .filter(|(s, _, _)| s == state)
158 .map(|(_, o, d)| (o, d))
159 .collect()
160 }
161}
162
163impl<S: Clone + Eq> BisimulationRelation<S> {
166 pub fn new() -> Self {
168 BisimulationRelation { pairs: Vec::new() }
169 }
170
171 pub fn add_pair(&mut self, s: S, t: S) {
173 if !self.pairs.iter().any(|(a, b)| *a == s && *b == t) {
174 self.pairs.push((s, t));
175 }
176 }
177
178 pub fn contains(&self, s: &S, t: &S) -> bool {
180 self.pairs.iter().any(|(a, b)| a == s && b == t)
181 }
182
183 pub fn len(&self) -> usize {
185 self.pairs.len()
186 }
187
188 pub fn is_empty(&self) -> bool {
190 self.pairs.is_empty()
191 }
192
193 pub fn check<O: Clone + Eq>(&self, coalgebra: &CoalgebraMap<S, O>) -> bool {
199 for (s, t) in &self.pairs {
200 for (obs, s_next) in coalgebra.transitions_from(s) {
202 let matched = coalgebra
203 .transitions_from(t)
204 .into_iter()
205 .any(|(o2, t_next)| o2 == obs && self.contains(s_next, t_next));
206 if !matched {
207 return false;
208 }
209 }
210 for (obs, t_next) in coalgebra.transitions_from(t) {
212 let matched = coalgebra
213 .transitions_from(s)
214 .into_iter()
215 .any(|(o2, s_next)| o2 == obs && self.contains(s_next, t_next));
216 if !matched {
217 return false;
218 }
219 }
220 }
221 true
222 }
223}
224
225pub fn greatest_fixed_point_approx<S: Clone + Eq>(
234 coalgebra: &CoalgebraMap<S, String>,
235 max_iters: usize,
236) -> BisimulationRelation<S> {
237 let mut states: Vec<S> = Vec::new();
239 for (s, _, d) in &coalgebra.transitions {
240 if !states.contains(s) {
241 states.push(s.clone());
242 }
243 if !states.contains(d) {
244 states.push(d.clone());
245 }
246 }
247
248 let mut relation = BisimulationRelation::new();
250 for s in &states {
251 for t in &states {
252 relation.add_pair(s.clone(), t.clone());
253 }
254 }
255
256 for _ in 0..max_iters {
257 let mut next = BisimulationRelation::new();
258 for (s, t) in &relation.pairs {
259 let s_ok = coalgebra.transitions_from(s).iter().all(|(obs, s_next)| {
261 coalgebra
262 .transitions_from(t)
263 .iter()
264 .any(|(o2, t_next)| *o2 == *obs && relation.contains(s_next, t_next))
265 });
266 let t_ok = coalgebra.transitions_from(t).iter().all(|(obs, t_next)| {
267 coalgebra
268 .transitions_from(s)
269 .iter()
270 .any(|(o2, s_next)| *o2 == *obs && relation.contains(s_next, t_next))
271 });
272 if s_ok && t_ok {
273 next.add_pair(s.clone(), t.clone());
274 }
275 }
276 if next.pairs.len() == relation.pairs.len() {
277 return next; }
279 relation = next;
280 }
281 relation
282}
283
284pub fn prove_bisimilar<S: Clone + Eq + std::fmt::Debug>(
289 s1: S,
290 s2: S,
291 coalgebra: &CoalgebraMap<S, String>,
292) -> Option<CoinductiveProof<S>> {
293 let max_iters = 100;
294 let gfp = greatest_fixed_point_approx(coalgebra, max_iters);
295 let mut progress = Vec::new();
296 progress.push(format!(
297 "Computed GFP with {} pairs after up to {} iterations",
298 gfp.pairs.len(),
299 max_iters
300 ));
301 if gfp.contains(&s1, &s2) {
302 progress.push(format!(
303 "Found ({:?}, {:?}) in the greatest bisimulation",
304 s1, s2
305 ));
306 Some(CoinductiveProof {
307 relation: gfp,
308 progress,
309 })
310 } else {
311 None
312 }
313}
314
315impl GreibachNormalForm {
318 pub fn new(rules: Vec<(String, char, Vec<String>)>) -> Self {
320 GreibachNormalForm { rules }
321 }
322
323 pub fn rules_for(&self, nt: &str) -> Vec<(char, &[String])> {
325 self.rules
326 .iter()
327 .filter(|(lhs, _, _)| lhs == nt)
328 .map(|(_, terminal, rhs)| (*terminal, rhs.as_slice()))
329 .collect()
330 }
331
332 pub fn derives(&self, start: &str, input: &[char]) -> bool {
337 self.derives_stack(vec![start.to_string()], input)
338 }
339
340 fn derives_stack(&self, stack: Vec<String>, input: &[char]) -> bool {
341 if stack.is_empty() {
342 return input.is_empty();
343 }
344 if input.is_empty() {
345 return false;
346 }
347 let top = &stack[0];
348 let rest_stack = stack[1..].to_vec();
349 for (terminal, rhs) in self.rules_for(top) {
350 if terminal == input[0] {
351 let mut new_stack: Vec<String> = rhs.to_vec();
352 new_stack.extend_from_slice(&rest_stack);
353 if self.derives_stack(new_stack, &input[1..]) {
354 return true;
355 }
356 }
357 }
358 false
359 }
360}
361
362impl<F: 'static> Codata<F> {
365 pub fn new(unfold: impl Fn() -> F + 'static) -> Self {
367 Codata {
368 unfold: Box::new(unfold),
369 }
370 }
371
372 pub fn unfold(&self) -> F {
374 (self.unfold)()
375 }
376}
377
378pub fn fibonacci_stream() -> LazyStream<u64> {
386 let mut fibs = Vec::with_capacity(94);
389 let (mut a, mut b) = (0u64, 1u64);
390 while let Some(next) = a.checked_add(b) {
391 fibs.push(a);
392 a = b;
393 b = next;
394 if fibs.len() >= 94 {
395 break;
396 }
397 }
398 fibs.push(a); LazyStream::new(fibs, Vec::new())
402}
403
404pub fn fibonacci_stream_n(n: usize) -> Vec<u64> {
407 fibonacci_stream().take(n)
408}
409
410#[cfg(test)]
413mod tests {
414 use super::*;
415
416 #[test]
419 fn constant_stream_take() {
420 let s = LazyStream::constant(42u32);
421 let v = s.take(5);
422 assert_eq!(v, vec![42, 42, 42, 42, 42]);
423 }
424
425 #[test]
426 fn cycle_stream() {
427 let s = LazyStream::cycle(vec![1u32, 2, 3]);
428 assert_eq!(s.take(7), vec![1, 2, 3, 1, 2, 3, 1]);
429 }
430
431 #[test]
432 fn nth_within_prefix() {
433 let s = LazyStream::new(vec![10u32, 20, 30], vec![1, 2]);
434 assert_eq!(s.nth(0), 10);
435 assert_eq!(s.nth(1), 20);
436 assert_eq!(s.nth(2), 30);
437 }
438
439 #[test]
440 fn nth_into_cycle() {
441 let s = LazyStream::new(vec![10u32], vec![1, 2, 3]);
442 assert_eq!(s.nth(1), 1);
443 assert_eq!(s.nth(2), 2);
444 assert_eq!(s.nth(3), 3);
445 assert_eq!(s.nth(4), 1); }
447
448 #[test]
449 fn take_empty() {
450 let s = LazyStream::constant(0u32);
451 assert_eq!(s.take(0), Vec::<u32>::new());
452 }
453
454 #[test]
455 fn map_stream() {
456 let s = LazyStream::cycle(vec![1u32, 2, 3]);
457 let doubled = s.map(|x| x * 2);
458 assert_eq!(doubled.take(6), vec![2, 4, 6, 2, 4, 6]);
459 }
460
461 #[test]
462 fn zip_streams_same_cycle() {
463 let s1 = LazyStream::cycle(vec![1u32, 2, 3]);
464 let s2 = LazyStream::cycle(vec![10u32, 20, 30]);
465 let sums = s1.zip(&s2, |a, b| a + b);
466 assert_eq!(sums.take(6), vec![11, 22, 33, 11, 22, 33]);
467 }
468
469 #[test]
470 fn zip_streams_different_cycles() {
471 let s1 = LazyStream::cycle(vec![1u32, 2]);
472 let s2 = LazyStream::cycle(vec![10u32, 20, 30]);
473 let sums = s1.zip(&s2, |a, b| a + b);
474 let v = sums.take(6);
476 assert_eq!(v[0], 11); assert_eq!(v[1], 22); assert_eq!(v[2], 31); assert_eq!(v[3], 12); assert_eq!(v[4], 21); assert_eq!(v[5], 32); }
483
484 #[test]
485 fn stream_with_prefix_and_cycle() {
486 let s = LazyStream::new(vec![0u32, 1], vec![2, 3]);
487 assert_eq!(s.take(8), vec![0, 1, 2, 3, 2, 3, 2, 3]);
488 }
489
490 #[test]
491 fn to_approx_depth_0() {
492 let s = LazyStream::constant(5u32);
493 let a = s.to_approx(0);
494 assert!(matches!(a, StreamApprox::Nil));
495 }
496
497 #[test]
498 fn to_approx_depth_3() {
499 let s = LazyStream::cycle(vec![1u32, 2, 3]);
500 let a = s.to_approx(3);
501 if let StreamApprox::Cons(node) = a {
502 assert_eq!(node.head, 1);
503 if let StreamApprox::Cons(n2) = *node.tail {
504 assert_eq!(n2.head, 2);
505 } else {
506 panic!("expected Cons at depth 2");
507 }
508 } else {
509 panic!("expected Cons at depth 1");
510 }
511 }
512
513 #[test]
516 fn fibonacci_first_10() {
517 let fibs = fibonacci_stream_n(10);
518 assert_eq!(fibs, vec![0, 1, 1, 2, 3, 5, 8, 13, 21, 34]);
519 }
520
521 #[test]
522 fn fibonacci_0th_element() {
523 let s = fibonacci_stream();
524 assert_eq!(s.nth(0), 0);
525 }
526
527 #[test]
528 fn fibonacci_1st_element() {
529 let s = fibonacci_stream();
530 assert_eq!(s.nth(1), 1);
531 }
532
533 #[test]
534 fn fibonacci_map() {
535 let s = fibonacci_stream();
536 let doubled: Vec<u64> = s.map(|x| x * 2).take(5);
537 assert_eq!(doubled, vec![0, 2, 2, 4, 6]);
538 }
539
540 #[test]
543 fn bisim_check_trivial_identity() {
544 let mut coalgebra: CoalgebraMap<u32, String> = CoalgebraMap::new();
546 coalgebra.add_transition(0, "a".to_string(), 0);
547 coalgebra.add_transition(1, "a".to_string(), 1);
548
549 let mut rel: BisimulationRelation<u32> = BisimulationRelation::new();
550 rel.add_pair(0, 1);
551 rel.add_pair(0, 0);
552 rel.add_pair(1, 1);
553
554 assert!(rel.check(&coalgebra));
555 }
556
557 #[test]
558 fn bisim_check_invalid() {
559 let mut coalgebra: CoalgebraMap<u32, String> = CoalgebraMap::new();
561 coalgebra.add_transition(0, "a".to_string(), 0);
562 coalgebra.add_transition(1, "b".to_string(), 1);
563
564 let mut rel: BisimulationRelation<u32> = BisimulationRelation::new();
565 rel.add_pair(0, 1);
566
567 assert!(!rel.check(&coalgebra));
568 }
569
570 #[test]
571 fn bisim_relation_contains() {
572 let mut rel: BisimulationRelation<i32> = BisimulationRelation::new();
573 rel.add_pair(1, 2);
574 assert!(rel.contains(&1, &2));
575 assert!(!rel.contains(&2, &1));
576 }
577
578 #[test]
581 fn gfp_simple_two_state() {
582 let mut coalgebra: CoalgebraMap<u32, String> = CoalgebraMap::new();
584 coalgebra.add_transition(0, "a".to_string(), 0);
585 coalgebra.add_transition(1, "a".to_string(), 1);
586
587 let gfp = greatest_fixed_point_approx(&coalgebra, 20);
588 assert!(gfp.contains(&0, &1));
589 assert!(gfp.contains(&1, &0));
590 }
591
592 #[test]
593 fn gfp_distinguishable_states_not_bisimilar() {
594 let mut coalgebra: CoalgebraMap<u32, String> = CoalgebraMap::new();
595 coalgebra.add_transition(0, "a".to_string(), 0);
596 coalgebra.add_transition(1, "b".to_string(), 1);
597
598 let gfp = greatest_fixed_point_approx(&coalgebra, 20);
599 assert!(!gfp.contains(&0, &1));
600 }
601
602 #[test]
603 fn prove_bisimilar_success() {
604 let mut coalgebra: CoalgebraMap<&str, String> = CoalgebraMap::new();
605 coalgebra.add_transition("p", "tick".to_string(), "p");
606 coalgebra.add_transition("q", "tick".to_string(), "q");
607
608 let proof = prove_bisimilar("p", "q", &coalgebra);
609 assert!(proof.is_some());
610 let proof = proof.expect("should be Some");
611 assert!(!proof.progress.is_empty());
612 assert!(proof.relation.contains(&"p", &"q"));
613 }
614
615 #[test]
616 fn prove_bisimilar_failure() {
617 let mut coalgebra: CoalgebraMap<&str, String> = CoalgebraMap::new();
618 coalgebra.add_transition("p", "a".to_string(), "p");
619 coalgebra.add_transition("q", "b".to_string(), "q");
620
621 let proof = prove_bisimilar("p", "q", &coalgebra);
622 assert!(proof.is_none());
623 }
624
625 #[test]
628 fn gnf_simple_grammar() {
629 let gnf = GreibachNormalForm::new(vec![
632 ("S".to_string(), 'a', vec!["S".to_string()]),
633 ("S".to_string(), 'a', vec![]),
634 ]);
635 assert!(gnf.derives("S", &['a', 'a', 'a']));
637 assert!(!gnf.derives("S", &['b']));
639 assert!(!gnf.derives("S", &[]));
641 }
642
643 #[test]
644 fn gnf_rules_for() {
645 let gnf = GreibachNormalForm::new(vec![
646 ("A".to_string(), 'x', vec!["B".to_string()]),
647 ("A".to_string(), 'y', vec![]),
648 ("B".to_string(), 'z', vec![]),
649 ]);
650 let a_rules = gnf.rules_for("A");
651 assert_eq!(a_rules.len(), 2);
652 }
653
654 #[test]
657 fn codata_unfold() {
658 let c = Codata::new(|| 42u32);
659 assert_eq!(c.unfold(), 42);
660 }
661
662 #[test]
663 fn codata_unfold_returns_constant() {
664 let c: Codata<u32> = Codata::new(|| 99u32);
666 assert_eq!(c.unfold(), 99);
668 assert_eq!(c.unfold(), 99);
669 }
670
671 #[test]
674 fn lcm_helper() {
675 assert_eq!(lcm(2, 3), 6);
676 assert_eq!(lcm(4, 6), 12);
677 assert_eq!(lcm(5, 5), 5);
678 assert_eq!(lcm(0, 5), 0);
679 }
680
681 #[test]
682 fn gcd_helper() {
683 assert_eq!(gcd(12, 8), 4);
684 assert_eq!(gcd(7, 3), 1);
685 assert_eq!(gcd(0, 5), 5);
686 }
687
688 #[test]
689 fn bisim_empty_relation_is_valid() {
690 let coalgebra: CoalgebraMap<u32, String> = CoalgebraMap::new();
691 let rel: BisimulationRelation<u32> = BisimulationRelation::new();
692 assert!(rel.check(&coalgebra));
694 }
695
696 #[test]
697 fn coalgebra_transitions_from() {
698 let mut c: CoalgebraMap<u32, String> = CoalgebraMap::new();
699 c.add_transition(0, "a".to_string(), 1);
700 c.add_transition(0, "b".to_string(), 2);
701 c.add_transition(1, "c".to_string(), 0);
702 let from_0 = c.transitions_from(&0);
703 assert_eq!(from_0.len(), 2);
704 }
705}