Skip to main content

argumentation/semantics/
stable.rs

1//! Stable extensions: conflict-free sets that attack every argument outside them.
2
3use super::subset_enum::{sorted_args_or_too_large, subset_from_bits};
4use crate::framework::ArgumentationFramework;
5use std::collections::HashSet;
6use std::hash::Hash;
7
8impl<A: Clone + Eq + Hash + Ord> ArgumentationFramework<A> {
9    /// Enumerate all stable extensions.
10    ///
11    /// A stable extension is a conflict-free set `S` such that every argument
12    /// not in `S` is attacked by some member of `S`. Stable extensions may not
13    /// exist (e.g., in odd cycles).
14    ///
15    /// Returns [`crate::Error::TooLarge`] for frameworks above the enumeration limit.
16    pub fn stable_extensions(&self) -> Result<Vec<HashSet<A>>, crate::Error> {
17        let args = sorted_args_or_too_large(self)?;
18        let n = args.len();
19        let mut results = Vec::new();
20        for bits in 0u64..(1u64 << n) {
21            let s = subset_from_bits(&args, bits);
22            if !self.is_conflict_free(&s) {
23                continue;
24            }
25            let attacks_all_outside = self
26                .arguments()
27                .filter(|a| !s.contains(*a))
28                .all(|a| self.attackers(a).iter().any(|att| s.contains(*att)));
29            if attacks_all_outside {
30                results.push(s);
31            }
32        }
33        Ok(results)
34    }
35}
36
37#[cfg(test)]
38mod tests {
39    use super::*;
40
41    #[test]
42    fn stable_of_figure_1_is_singleton_ac() {
43        let mut af = ArgumentationFramework::new();
44        af.add_argument("a");
45        af.add_argument("b");
46        af.add_argument("c");
47        af.add_attack(&"a", &"b").unwrap();
48        af.add_attack(&"b", &"c").unwrap();
49        let se = af.stable_extensions().unwrap();
50        assert_eq!(se.len(), 1);
51        let expected: HashSet<&str> = ["a", "c"].into_iter().collect();
52        assert!(se.contains(&expected));
53    }
54
55    #[test]
56    fn stable_of_odd_cycle_is_empty() {
57        let mut af = ArgumentationFramework::new();
58        af.add_argument("a");
59        af.add_argument("b");
60        af.add_argument("c");
61        af.add_attack(&"a", &"b").unwrap();
62        af.add_attack(&"b", &"c").unwrap();
63        af.add_attack(&"c", &"a").unwrap();
64        let se = af.stable_extensions().unwrap();
65        assert!(se.is_empty());
66    }
67
68    #[test]
69    fn stable_subset_of_preferred() {
70        let mut af = ArgumentationFramework::new();
71        af.add_argument("a");
72        af.add_argument("b");
73        af.add_attack(&"a", &"b").unwrap();
74        af.add_attack(&"b", &"a").unwrap();
75        let se = af.stable_extensions().unwrap();
76        let pe = af.preferred_extensions().unwrap();
77        for s in &se {
78            assert!(pe.contains(s));
79        }
80    }
81}