argumentation_weighted/
semantics.rs1use crate::error::Error;
10use crate::framework::WeightedFramework;
11use crate::reduce::dunne_residuals;
12use crate::types::Budget;
13use std::collections::HashSet;
14use std::fmt::Debug;
15use std::hash::Hash;
16
17pub fn grounded_at_budget<A>(
20 framework: &WeightedFramework<A>,
21 budget: Budget,
22) -> Result<HashSet<A>, Error>
23where
24 A: Clone + Eq + Hash + Debug + Ord,
25{
26 let mut union: HashSet<A> = HashSet::new();
27 for af in dunne_residuals(framework, budget)? {
28 union.extend(af.grounded_extension());
29 }
30 Ok(union)
31}
32
33pub fn complete_at_budget<A>(
35 framework: &WeightedFramework<A>,
36 budget: Budget,
37) -> Result<Vec<HashSet<A>>, Error>
38where
39 A: Clone + Eq + Hash + Debug + Ord,
40{
41 let mut out: Vec<HashSet<A>> = Vec::new();
42 for af in dunne_residuals(framework, budget)? {
43 for ext in af.complete_extensions()? {
44 if !out.contains(&ext) {
45 out.push(ext);
46 }
47 }
48 }
49 Ok(out)
50}
51
52pub fn preferred_at_budget<A>(
54 framework: &WeightedFramework<A>,
55 budget: Budget,
56) -> Result<Vec<HashSet<A>>, Error>
57where
58 A: Clone + Eq + Hash + Debug + Ord,
59{
60 let mut out: Vec<HashSet<A>> = Vec::new();
61 for af in dunne_residuals(framework, budget)? {
62 for ext in af.preferred_extensions()? {
63 if !out.contains(&ext) {
64 out.push(ext);
65 }
66 }
67 }
68 Ok(out)
69}
70
71pub fn stable_at_budget<A>(
74 framework: &WeightedFramework<A>,
75 budget: Budget,
76) -> Result<Vec<HashSet<A>>, Error>
77where
78 A: Clone + Eq + Hash + Debug + Ord,
79{
80 let mut out: Vec<HashSet<A>> = Vec::new();
81 for af in dunne_residuals(framework, budget)? {
82 for ext in af.stable_extensions()? {
83 if !out.contains(&ext) {
84 out.push(ext);
85 }
86 }
87 }
88 Ok(out)
89}
90
91pub fn is_credulously_accepted_at<A>(
94 framework: &WeightedFramework<A>,
95 target: &A,
96 budget: Budget,
97) -> Result<bool, Error>
98where
99 A: Clone + Eq + Hash + Debug + Ord,
100{
101 for af in dunne_residuals(framework, budget)? {
102 if af.preferred_extensions()?.iter().any(|e| e.contains(target)) {
103 return Ok(true);
104 }
105 }
106 Ok(false)
107}
108
109pub fn is_skeptically_accepted_at<A>(
113 framework: &WeightedFramework<A>,
114 target: &A,
115 budget: Budget,
116) -> Result<bool, Error>
117where
118 A: Clone + Eq + Hash + Debug + Ord,
119{
120 for af in dunne_residuals(framework, budget)? {
126 let exts = af.preferred_extensions()?;
127 if exts.is_empty() {
128 return Ok(false);
129 }
130 if !exts.iter().all(|e| e.contains(target)) {
131 return Ok(false);
132 }
133 }
134 Ok(true)
135}
136
137#[cfg(test)]
138mod tests {
139 use super::*;
140
141 #[test]
142 fn grounded_at_zero_budget_matches_dung() {
143 let mut wf = WeightedFramework::new();
144 wf.add_weighted_attack("a", "b", 0.5).unwrap();
145 wf.add_weighted_attack("b", "c", 0.5).unwrap();
146 let grounded = grounded_at_budget(&wf, Budget::zero()).unwrap();
147 assert!(grounded.contains(&"a"));
148 assert!(grounded.contains(&"c"));
149 assert!(!grounded.contains(&"b"));
150 }
151
152 #[test]
153 fn grounded_union_widens_as_budget_grows() {
154 let mut wf = WeightedFramework::new();
155 wf.add_weighted_attack("a", "b", 0.5).unwrap();
156 let g0 = grounded_at_budget(&wf, Budget::zero()).unwrap();
157 let g1 = grounded_at_budget(&wf, Budget::new(1.0).unwrap()).unwrap();
158 assert!(g0.is_subset(&g1));
161 assert!(g1.contains(&"b"));
162 }
163
164 #[test]
165 fn credulous_acceptance_monotone_in_budget() {
166 let mut wf = WeightedFramework::new();
167 wf.add_weighted_attack("a", "b", 0.3).unwrap();
168 wf.add_weighted_attack("b", "c", 0.7).unwrap();
169 let at0 = is_credulously_accepted_at(&wf, &"b", Budget::zero()).unwrap();
173 let at03 = is_credulously_accepted_at(&wf, &"b", Budget::new(0.3).unwrap()).unwrap();
174 assert!(!at0);
175 assert!(at03);
176 }
177
178 #[test]
179 fn skeptical_true_for_grounded_singleton() {
180 let mut wf = WeightedFramework::new();
181 wf.add_weighted_attack("a", "b", 0.5).unwrap();
182 assert!(is_skeptically_accepted_at(&wf, &"a", Budget::zero()).unwrap());
185 assert!(!is_skeptically_accepted_at(&wf, &"b", Budget::zero()).unwrap());
186 }
187
188 #[test]
189 fn preferred_at_budget_is_union_across_residuals() {
190 let mut wf = WeightedFramework::new();
191 wf.add_weighted_attack("a", "b", 0.2).unwrap();
192 wf.add_weighted_attack("b", "c", 0.4).unwrap();
193 let at0 = preferred_at_budget(&wf, Budget::zero()).unwrap();
194 assert!(at0.iter().any(|e| e.contains("a") && e.contains("c")));
195
196 let at02 = preferred_at_budget(&wf, Budget::new(0.2).unwrap()).unwrap();
197 let union: std::collections::HashSet<&str> =
198 at02.iter().flat_map(|e| e.iter().copied()).collect();
199 assert!(union.contains("b"), "b should be reachable at β=0.2");
200 assert!(union.contains("c"));
201 }
202
203 #[test]
204 fn preferred_at_budget_large_enough_accepts_all() {
205 let mut wf = WeightedFramework::new();
206 wf.add_weighted_attack("a", "b", 0.5).unwrap();
207 let at_big = preferred_at_budget(&wf, Budget::new(10.0).unwrap()).unwrap();
208 let union: std::collections::HashSet<&str> =
209 at_big.iter().flat_map(|e| e.iter().copied()).collect();
210 assert!(union.contains("a"));
211 assert!(union.contains("b"));
212 }
213}