qualia_core_db/modalities/argumentation/
bipolar.rs1use super::{ArgumentationFramework, Attack, AttackType};
8use std::collections::HashSet;
9
10#[derive(Debug, Clone)]
12pub struct BipolarFramework {
13 pub af: ArgumentationFramework,
14 pub supports: Vec<(u64, u64)>,
16}
17
18impl BipolarFramework {
19 pub fn new(af: ArgumentationFramework) -> Self {
20 Self {
21 af,
22 supports: Vec::new(),
23 }
24 }
25
26 pub fn add_support(&mut self, supporter: u64, supported: u64) {
28 self.supports.push((supporter, supported));
29 }
30
31 pub fn support_reaches(&self, from: u64, to: u64) -> bool {
33 let mut stack = vec![from];
34 let mut seen = HashSet::new();
35 seen.insert(from);
36 while let Some(x) = stack.pop() {
37 for &(s, t) in &self.supports {
38 if s == x {
39 if t == to {
40 return true;
41 }
42 if seen.insert(t) {
43 stack.push(t);
44 }
45 }
46 }
47 }
48 false
49 }
50
51 pub fn to_dung(&self) -> ArgumentationFramework {
54 let mk = |a: u64, b: u64| Attack {
55 attacker: a,
56 target: b,
57 attack_type: AttackType::Rebuttal,
58 strength: 1.0,
59 };
60 let mut out = ArgumentationFramework::new();
61 for arg in self.af.arguments.values() {
62 out.add_argument(arg.clone());
63 }
64 for atk in &self.af.attacks {
65 out.add_attack(atk.clone());
66 }
67 let ids: Vec<u64> = self.af.arguments.keys().copied().collect();
68
69 for atk in &self.af.attacks {
70 let (b, c) = (atk.attacker, atk.target);
71 for &a in &ids {
73 if a != b && self.support_reaches(a, b) {
74 out.add_attack(mk(a, c));
75 }
76 }
77 let (a, bb) = (atk.attacker, atk.target);
79 for &c in &ids {
80 if c != bb && self.support_reaches(c, bb) {
81 out.add_attack(mk(a, c));
82 }
83 }
84 }
85 out
86 }
87
88 pub fn grounded_extension(&self) -> HashSet<u64> {
90 self.to_dung().grounded_extension()
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::super::{Argument, Attack, AttackType};
97 use super::*;
98 use crate::NQuin;
99
100 fn arg(id: u64) -> Argument {
101 Argument::new(id, String::new(), Vec::new(), NQuin::default())
102 }
103
104 #[test]
105 fn support_induces_a_supported_attack() {
106 let mut af = ArgumentationFramework::new();
108 af.add_argument(arg(1)); af.add_argument(arg(2)); af.add_argument(arg(3)); af.add_attack(Attack {
112 attacker: 2,
113 target: 3,
114 attack_type: AttackType::Rebuttal,
115 strength: 1.0,
116 });
117
118 let mut bf = BipolarFramework::new(af);
119 bf.add_support(1, 2); assert!(bf.support_reaches(1, 2));
121
122 let dung = bf.to_dung();
123 assert!(
125 dung.attacks
126 .iter()
127 .any(|atk| atk.attacker == 1 && atk.target == 3),
128 "a supports b, b attacks c ⇒ a attacks c"
129 );
130 let g = bf.grounded_extension();
132 assert!(
133 !g.contains(&3),
134 "c is defeated through the support-backed attack"
135 );
136 }
137
138 #[test]
139 fn support_paths_are_transitive() {
140 let af = {
141 let mut a = ArgumentationFramework::new();
142 a.add_argument(arg(1));
143 a.add_argument(arg(2));
144 a.add_argument(arg(3));
145 a
146 };
147 let mut bf = BipolarFramework::new(af);
148 bf.add_support(1, 2);
149 bf.add_support(2, 3);
150 assert!(bf.support_reaches(1, 3), "support is transitive (1→2→3)");
151 assert!(!bf.support_reaches(3, 1));
152 }
153}