Skip to main content

argumentation_weighted_bipolar/
semantics.rs

1//! Acceptance semantics for weighted bipolar frameworks under Amgoud
2//! 2008 + Dunne 2011: iterate every β-inconsistent residual bipolar
3//! framework and aggregate across them (OR for credulous, AND for
4//! skeptical).
5
6use crate::error::Error;
7use crate::framework::WeightedBipolarFramework;
8use crate::reduce::wbipolar_residuals;
9use crate::types::Budget;
10use argumentation_bipolar::bipolar_preferred_extensions;
11use std::fmt::Debug;
12use std::hash::Hash;
13
14/// `target` is **β-credulously accepted** iff it belongs to some
15/// bipolar-preferred extension of some β-inconsistent residual.
16pub fn is_credulously_accepted_at<A>(
17    framework: &WeightedBipolarFramework<A>,
18    target: &A,
19    budget: Budget,
20) -> Result<bool, Error>
21where
22    A: Clone + Eq + Hash + Debug + Ord,
23{
24    for bf in wbipolar_residuals(framework, budget)? {
25        let exts = bipolar_preferred_extensions(&bf)?;
26        if exts.iter().any(|e| e.contains(target)) {
27            return Ok(true);
28        }
29    }
30    Ok(false)
31}
32
33/// `target` is **β-skeptically accepted** iff it belongs to every
34/// bipolar-preferred extension of every β-inconsistent residual.
35/// Returns `false` when any residual has no preferred extensions.
36pub fn is_skeptically_accepted_at<A>(
37    framework: &WeightedBipolarFramework<A>,
38    target: &A,
39    budget: Budget,
40) -> Result<bool, Error>
41where
42    A: Clone + Eq + Hash + Debug + Ord,
43{
44    let residuals = wbipolar_residuals(framework, budget)?;
45    // `wbipolar_residuals` always yields at least the empty-subset
46    // residual (cost 0 ≤ any non-negative β), so `residuals` is
47    // never empty in practice. We still guard against `exts.is_empty()`
48    // per residual because a bipolar framework with cyclic attacks can
49    // have no preferred extensions.
50    for bf in residuals {
51        let exts = bipolar_preferred_extensions(&bf)?;
52        if exts.is_empty() {
53            return Ok(false);
54        }
55        if !exts.iter().all(|e| e.contains(target)) {
56            return Ok(false);
57        }
58    }
59    Ok(true)
60}
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65
66    #[test]
67    fn credulous_at_zero_budget_matches_bipolar_preferred() {
68        // a attacks b; β = 0 ⇒ unique residual = original bipolar framework.
69        // Bipolar preferred = { {a} }. a is credulous, b is not.
70        let mut wbf = WeightedBipolarFramework::new();
71        wbf.add_weighted_attack("a", "b", 0.5).unwrap();
72        assert!(is_credulously_accepted_at(&wbf, &"a", Budget::zero()).unwrap());
73        assert!(!is_credulously_accepted_at(&wbf, &"b", Budget::zero()).unwrap());
74    }
75
76    #[test]
77    fn tolerating_a_support_breaks_support_closure() {
78        // a → b (support, weight 0.3). c attacks b (attack, weight 0.6).
79        // Pins monotonicity: if b accepted at β=0, still accepted at β=0.3.
80        let mut wbf = WeightedBipolarFramework::new();
81        wbf.add_weighted_support("a", "b", 0.3).unwrap();
82        wbf.add_weighted_attack("c", "b", 0.6).unwrap();
83        let at0 = is_credulously_accepted_at(&wbf, &"b", Budget::zero()).unwrap();
84        let at_drop_support =
85            is_credulously_accepted_at(&wbf, &"b", Budget::new(0.3).unwrap()).unwrap();
86        if at0 {
87            assert!(at_drop_support, "credulous monotonicity violated");
88        }
89    }
90
91    #[test]
92    fn skeptical_accepts_unattacked_self_supporter() {
93        let mut wbf = WeightedBipolarFramework::new();
94        wbf.add_argument("a");
95        // Sole residual: {a}, only preferred extension = {a}.
96        assert!(is_skeptically_accepted_at(&wbf, &"a", Budget::zero()).unwrap());
97    }
98
99    #[test]
100    fn credulous_monotone_in_budget() {
101        let mut wbf = WeightedBipolarFramework::new();
102        wbf.add_weighted_attack("a", "b", 0.4).unwrap();
103        wbf.add_weighted_attack("c", "a", 0.6).unwrap();
104        let at0 = is_credulously_accepted_at(&wbf, &"b", Budget::zero()).unwrap();
105        let at05 = is_credulously_accepted_at(&wbf, &"b", Budget::new(0.5).unwrap()).unwrap();
106        if at0 {
107            assert!(at05);
108        }
109    }
110}