Skip to main content

argumentation/semantics/
grounded.rs

1//! Grounded extension: least fixed point of the characteristic function.
2//!
3//! Per Dung 1995 ยง3: the grounded extension is the unique least complete
4//! extension. It is computed by iterating the characteristic function
5//! `F(S) = {a | S defends a}` starting from the empty set until fixed.
6
7use crate::framework::ArgumentationFramework;
8use std::collections::HashSet;
9use std::hash::Hash;
10
11impl<A: Clone + Eq + Hash> ArgumentationFramework<A> {
12    /// Compute the grounded extension.
13    ///
14    /// Returns the unique least complete extension (always exists, always unique).
15    pub fn grounded_extension(&self) -> HashSet<A> {
16        let mut current: HashSet<A> = HashSet::new();
17        loop {
18            let next: HashSet<A> = self
19                .arguments()
20                .filter(|a| self.defends(&current, *a))
21                .cloned()
22                .collect();
23            if next == current {
24                return current;
25            }
26            current = next;
27        }
28    }
29}
30
31#[cfg(test)]
32mod tests {
33    use super::*;
34
35    #[test]
36    fn grounded_of_empty_is_empty() {
37        let af: ArgumentationFramework<&str> = ArgumentationFramework::new();
38        assert_eq!(af.grounded_extension(), HashSet::new());
39    }
40
41    #[test]
42    fn grounded_includes_unattacked_arguments() {
43        // a is unattacked -> a is in grounded
44        let mut af = ArgumentationFramework::new();
45        af.add_argument("a");
46        let g = af.grounded_extension();
47        let expected: HashSet<&str> = ["a"].into_iter().collect();
48        assert_eq!(g, expected);
49    }
50
51    #[test]
52    fn grounded_of_figure_1_is_a_and_c() {
53        let mut af = ArgumentationFramework::new();
54        af.add_argument("a");
55        af.add_argument("b");
56        af.add_argument("c");
57        af.add_attack(&"a", &"b").unwrap();
58        af.add_attack(&"b", &"c").unwrap();
59        let g = af.grounded_extension();
60        let expected: HashSet<&str> = ["a", "c"].into_iter().collect();
61        assert_eq!(g, expected);
62    }
63
64    #[test]
65    fn grounded_of_odd_cycle_is_empty() {
66        let mut af = ArgumentationFramework::new();
67        af.add_argument("a");
68        af.add_argument("b");
69        af.add_argument("c");
70        af.add_attack(&"a", &"b").unwrap();
71        af.add_attack(&"b", &"c").unwrap();
72        af.add_attack(&"c", &"a").unwrap();
73        assert_eq!(af.grounded_extension(), HashSet::new());
74    }
75
76    #[test]
77    fn grounded_of_even_cycle_is_empty() {
78        let mut af = ArgumentationFramework::new();
79        af.add_argument("a");
80        af.add_argument("b");
81        af.add_attack(&"a", &"b").unwrap();
82        af.add_attack(&"b", &"a").unwrap();
83        assert_eq!(af.grounded_extension(), HashSet::new());
84    }
85}