argumentation_bipolar/
semantics.rs1use crate::error::Error;
25use crate::flatten::flatten;
26use crate::framework::BipolarFramework;
27use std::collections::HashSet;
28use std::fmt::Debug;
29use std::hash::Hash;
30
31#[must_use]
35pub fn is_support_closed<A>(framework: &BipolarFramework<A>, extension: &HashSet<A>) -> bool
36where
37 A: Clone + Eq + Hash,
38{
39 for a in extension {
40 for supporter in framework.direct_supporters(a) {
41 if !extension.contains(supporter) {
42 return false;
43 }
44 }
45 }
46 true
47}
48
49pub fn bipolar_preferred_extensions<A>(
57 framework: &BipolarFramework<A>,
58) -> Result<Vec<HashSet<A>>, Error>
59where
60 A: Clone + Eq + Hash + Debug + Ord,
61{
62 let af = flatten(framework)?;
63 let dung_preferred = af.preferred_extensions()?;
64 let filtered: Vec<HashSet<A>> = dung_preferred
65 .into_iter()
66 .filter(|ext| is_support_closed(framework, ext))
67 .collect();
68 Ok(filtered)
69}
70
71pub fn bipolar_complete_extensions<A>(
73 framework: &BipolarFramework<A>,
74) -> Result<Vec<HashSet<A>>, Error>
75where
76 A: Clone + Eq + Hash + Debug + Ord,
77{
78 let af = flatten(framework)?;
79 let dung_complete = af.complete_extensions()?;
80 let filtered: Vec<HashSet<A>> = dung_complete
81 .into_iter()
82 .filter(|ext| is_support_closed(framework, ext))
83 .collect();
84 Ok(filtered)
85}
86
87pub fn bipolar_stable_extensions<A>(
89 framework: &BipolarFramework<A>,
90) -> Result<Vec<HashSet<A>>, Error>
91where
92 A: Clone + Eq + Hash + Debug + Ord,
93{
94 let af = flatten(framework)?;
95 let dung_stable = af.stable_extensions()?;
96 let filtered: Vec<HashSet<A>> = dung_stable
97 .into_iter()
98 .filter(|ext| is_support_closed(framework, ext))
99 .collect();
100 Ok(filtered)
101}
102
103pub fn bipolar_grounded_extension<A>(framework: &BipolarFramework<A>) -> Result<HashSet<A>, Error>
113where
114 A: Clone + Eq + Hash + Debug + Ord,
115{
116 let af = flatten(framework)?;
117 let mut grounded = af.grounded_extension();
118 loop {
121 let to_remove: Vec<A> = grounded
122 .iter()
123 .filter(|a| {
124 framework
125 .direct_supporters(a)
126 .iter()
127 .any(|s| !grounded.contains(*s))
128 })
129 .cloned()
130 .collect();
131 if to_remove.is_empty() {
132 break;
133 }
134 for a in to_remove {
135 grounded.remove(&a);
136 }
137 }
138 Ok(grounded)
139}
140
141#[cfg(test)]
142mod tests {
143 use super::*;
144
145 #[test]
146 fn empty_extension_is_support_closed() {
147 let bf: BipolarFramework<&str> = BipolarFramework::new();
148 let ext: HashSet<&str> = HashSet::new();
149 assert!(is_support_closed(&bf, &ext));
150 }
151
152 #[test]
153 fn extension_missing_supporter_fails_closure() {
154 let mut bf = BipolarFramework::new();
155 bf.add_support("a", "b").unwrap();
156 let ext: HashSet<&str> = ["b"].into_iter().collect();
157 assert!(!is_support_closed(&bf, &ext));
158 }
159
160 #[test]
161 fn extension_containing_supporter_passes_closure() {
162 let mut bf = BipolarFramework::new();
163 bf.add_support("a", "b").unwrap();
164 let ext: HashSet<&str> = ["a", "b"].into_iter().collect();
165 assert!(is_support_closed(&bf, &ext));
166 }
167
168 #[test]
169 fn bipolar_preferred_filters_unsupported_candidates() {
170 let mut bf = BipolarFramework::new();
174 bf.add_support("a", "b").unwrap();
175 let prefs = bipolar_preferred_extensions(&bf).unwrap();
176 assert_eq!(prefs.len(), 1);
177 assert_eq!(prefs[0].len(), 2);
178 assert!(prefs[0].contains(&"a"));
179 assert!(prefs[0].contains(&"b"));
180 }
181
182 #[test]
183 fn grounded_repair_removes_unsupported_arguments() {
184 let mut bf = BipolarFramework::new();
189 bf.add_support("a", "b").unwrap();
190 bf.add_attack("c", "a");
191 let grounded = bipolar_grounded_extension(&bf).unwrap();
192 assert!(grounded.contains(&"c"));
193 assert!(!grounded.contains(&"a"));
194 assert!(
195 !grounded.contains(&"b"),
196 "b should be removed because its supporter a is missing"
197 );
198 }
199}