Skip to main content

argumentation_weighted_bipolar/
framework.rs

1//! `WeightedBipolarFramework<A>`: arguments, weighted attacks, weighted supports.
2
3use crate::error::Error;
4use crate::types::{AttackWeight, WeightedAttack, WeightedSupport};
5use std::collections::HashSet;
6use std::hash::Hash;
7
8/// A weighted bipolar argumentation framework.
9///
10/// Stores arguments and two lists of weighted directed edges — attacks
11/// and supports — with non-negative finite weights.
12#[derive(Debug, Clone)]
13pub struct WeightedBipolarFramework<A: Clone + Eq + Hash> {
14    arguments: HashSet<A>,
15    attacks: Vec<WeightedAttack<A>>,
16    supports: Vec<WeightedSupport<A>>,
17}
18
19impl<A: Clone + Eq + Hash> Default for WeightedBipolarFramework<A> {
20    fn default() -> Self {
21        Self::new()
22    }
23}
24
25impl<A: Clone + Eq + Hash> WeightedBipolarFramework<A> {
26    /// Create an empty framework.
27    #[must_use]
28    pub fn new() -> Self {
29        Self {
30            arguments: HashSet::new(),
31            attacks: Vec::new(),
32            supports: Vec::new(),
33        }
34    }
35
36    /// Add an argument. Adding an existing argument is a no-op.
37    pub fn add_argument(&mut self, a: A) {
38        self.arguments.insert(a);
39    }
40
41    /// Add a weighted attack. Both endpoints are implicitly added.
42    pub fn add_weighted_attack(
43        &mut self,
44        attacker: A,
45        target: A,
46        weight: f64,
47    ) -> Result<(), Error> {
48        let w = AttackWeight::new(weight).map_err(|_| Error::InvalidWeight { weight })?;
49        self.arguments.insert(attacker.clone());
50        self.arguments.insert(target.clone());
51        self.attacks.push(WeightedAttack {
52            attacker,
53            target,
54            weight: w,
55        });
56        Ok(())
57    }
58
59    /// Add a weighted support. Both endpoints are implicitly added.
60    /// Returns [`Error::IllegalSelfSupport`] if `supporter == supported`.
61    pub fn add_weighted_support(
62        &mut self,
63        supporter: A,
64        supported: A,
65        weight: f64,
66    ) -> Result<(), Error> {
67        let support = WeightedSupport::new(supporter.clone(), supported.clone(), weight)?;
68        self.arguments.insert(supporter);
69        self.arguments.insert(supported);
70        self.supports.push(support);
71        Ok(())
72    }
73
74    /// Iterate arguments.
75    pub fn arguments(&self) -> impl Iterator<Item = &A> {
76        self.arguments.iter()
77    }
78
79    /// Iterate weighted attacks.
80    pub fn attacks(&self) -> impl Iterator<Item = &WeightedAttack<A>> {
81        self.attacks.iter()
82    }
83
84    /// Iterate weighted supports.
85    pub fn supports(&self) -> impl Iterator<Item = &WeightedSupport<A>> {
86        self.supports.iter()
87    }
88
89    /// Total edge count (attacks + supports).
90    #[must_use]
91    pub fn edge_count(&self) -> usize {
92        self.attacks.len() + self.supports.len()
93    }
94
95    /// Argument count.
96    #[must_use]
97    pub fn argument_count(&self) -> usize {
98        self.arguments.len()
99    }
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn new_framework_is_empty() {
108        let wbf: WeightedBipolarFramework<&str> = WeightedBipolarFramework::new();
109        assert_eq!(wbf.argument_count(), 0);
110        assert_eq!(wbf.edge_count(), 0);
111    }
112
113    #[test]
114    fn adding_weighted_attack_adds_endpoints() {
115        let mut wbf = WeightedBipolarFramework::new();
116        wbf.add_weighted_attack("a", "b", 0.5).unwrap();
117        assert_eq!(wbf.argument_count(), 2);
118        assert_eq!(wbf.edge_count(), 1);
119    }
120
121    #[test]
122    fn adding_weighted_support_adds_endpoints() {
123        let mut wbf = WeightedBipolarFramework::new();
124        wbf.add_weighted_support("a", "b", 0.5).unwrap();
125        assert_eq!(wbf.argument_count(), 2);
126        assert_eq!(wbf.edge_count(), 1);
127    }
128
129    #[test]
130    fn invalid_attack_weight_rejected() {
131        let mut wbf = WeightedBipolarFramework::new();
132        let err = wbf.add_weighted_attack("a", "b", -0.5).unwrap_err();
133        assert!(matches!(err, Error::InvalidWeight { .. }));
134    }
135
136    #[test]
137    fn self_support_rejected() {
138        let mut wbf = WeightedBipolarFramework::new();
139        let err = wbf.add_weighted_support("a", "a", 0.5).unwrap_err();
140        assert!(matches!(err, Error::IllegalSelfSupport));
141    }
142
143    #[test]
144    fn add_argument_idempotent() {
145        let mut wbf = WeightedBipolarFramework::new();
146        wbf.add_argument("a");
147        wbf.add_argument("a");
148        assert_eq!(wbf.argument_count(), 1);
149    }
150}