argumentation_bipolar/
derived.rs1use crate::framework::BipolarFramework;
21use std::collections::HashSet;
22use std::hash::Hash;
23
24fn support_closure<A: Clone + Eq + Hash>(
28 framework: &BipolarFramework<A>,
29) -> std::collections::HashMap<A, HashSet<A>> {
30 use std::collections::{HashMap, VecDeque};
31
32 let mut closure: HashMap<A, HashSet<A>> = HashMap::new();
33 for arg in framework.arguments() {
34 closure.insert(arg.clone(), HashSet::new());
35 }
36
37 for start in framework.arguments() {
40 let mut visited: HashSet<A> = HashSet::new();
41 let mut frontier: VecDeque<A> = VecDeque::new();
42 frontier.push_back(start.clone());
43 while let Some(current) = frontier.pop_front() {
44 for (sup, supd) in framework.supports() {
45 if *sup == current && visited.insert(supd.clone()) {
46 frontier.push_back(supd.clone());
47 }
48 }
49 }
50 closure.insert(start.clone(), visited);
51 }
52
53 closure
54}
55
56pub fn closed_attacks<A>(framework: &BipolarFramework<A>) -> HashSet<(A, A)>
66where
67 A: Clone + Eq + Hash,
68{
69 let support_cl = support_closure(framework);
70
71 let mut closed: HashSet<(A, A)> = HashSet::new();
72
73 for (a, b) in framework.attacks() {
75 closed.insert((a.clone(), b.clone()));
76 }
77
78 for (x, b) in framework.attacks() {
82 for (a, supported_by_a) in &support_cl {
83 if supported_by_a.contains(x) {
84 closed.insert((a.clone(), b.clone()));
85 }
86 }
87 }
88
89 for (a, x) in framework.attacks() {
92 if let Some(downstream) = support_cl.get(x) {
93 for c in downstream {
94 closed.insert((a.clone(), c.clone()));
95 }
96 }
97 }
98
99 for (x, y) in framework.attacks() {
105 for (a, supported_by_a) in &support_cl {
106 if !supported_by_a.contains(x) {
107 continue;
108 }
109 if let Some(downstream_of_y) = support_cl.get(y) {
110 for c in downstream_of_y {
111 closed.insert((a.clone(), c.clone()));
112 }
113 }
114 }
115 }
116
117 closed
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123
124 #[test]
125 fn direct_attack_is_preserved() {
126 let mut bf = BipolarFramework::new();
127 bf.add_attack("a", "b");
128 let closed = closed_attacks(&bf);
129 assert!(closed.contains(&("a", "b")));
130 assert_eq!(closed.len(), 1);
131 }
132
133 #[test]
134 fn supported_attack_rule_fires() {
135 let mut bf = BipolarFramework::new();
137 bf.add_support("a", "x").unwrap();
138 bf.add_attack("x", "b");
139 let closed = closed_attacks(&bf);
140 assert!(closed.contains(&("x", "b"))); assert!(closed.contains(&("a", "b"))); }
143
144 #[test]
145 fn secondary_attack_rule_fires() {
146 let mut bf = BipolarFramework::new();
148 bf.add_attack("a", "x");
149 bf.add_support("x", "c").unwrap();
150 let closed = closed_attacks(&bf);
151 assert!(closed.contains(&("a", "x"))); assert!(closed.contains(&("a", "c"))); }
154
155 #[test]
156 fn two_sided_closure_composes_supported_and_secondary() {
157 let mut bf = BipolarFramework::new();
160 bf.add_support("a", "x").unwrap();
161 bf.add_attack("x", "y");
162 bf.add_support("y", "c").unwrap();
163 let closed = closed_attacks(&bf);
164 assert!(closed.contains(&("x", "y")));
165 assert!(closed.contains(&("a", "y"))); assert!(closed.contains(&("x", "c"))); assert!(closed.contains(&("a", "c"))); }
169
170 #[test]
171 fn transitive_support_chain_propagates_supported_attack() {
172 let mut bf = BipolarFramework::new();
174 bf.add_support("a", "b").unwrap();
175 bf.add_support("b", "x").unwrap();
176 bf.add_attack("x", "target");
177 let closed = closed_attacks(&bf);
178 assert!(closed.contains(&("x", "target")));
179 assert!(closed.contains(&("b", "target")));
180 assert!(closed.contains(&("a", "target")));
181 }
182
183 #[test]
184 fn isolated_arguments_produce_no_derived_attacks() {
185 let mut bf = BipolarFramework::new();
186 bf.add_argument("a");
187 bf.add_argument("b");
188 bf.add_argument("c");
189 assert!(closed_attacks(&bf).is_empty());
190 }
191}