Skip to main content

argumentation_values/
acceptance.rs

1//! Subjective and objective acceptance over the space of audiences.
2//!
3//! Per Bench-Capon (2003):
4//! - **Subjective acceptance**: arg is accepted by *some* audience.
5//! - **Objective acceptance**: arg is accepted by *every* audience.
6//!
7//! Both queries enumerate the linear extensions of the partial order
8//! defined by the value set. The number of linear extensions is bounded
9//! above by `n!` for `n` distinct values; we hard-cap at 6 (= 720) to
10//! keep these queries tractable for narrative-scale frameworks.
11//!
12//! Past the cap, methods return [`Error::AudienceTooLarge`].
13
14use crate::error::Error;
15use crate::framework::ValueBasedFramework;
16use crate::types::{Audience, Value};
17use std::hash::Hash;
18
19/// Hard cap on distinct values for subjective/objective acceptance.
20/// At 6 values, worst-case linear-extension count is 720.
21pub const ENUMERATION_LIMIT: usize = 6;
22
23/// Returns `Ok(true)` iff `arg` is accepted by *some* total ordering of
24/// the values mentioned in the framework.
25///
26/// Strategy: enumerate every permutation of the value set, build the
27/// corresponding total-order [`Audience`], and check `accepted_for`.
28/// Returns true on the first acceptance; otherwise false.
29pub fn subjectively_accepted<A>(
30    vaf: &ValueBasedFramework<A>,
31    arg: &A,
32) -> Result<bool, Error>
33where
34    A: Clone + Eq + Hash + Ord + std::fmt::Debug,
35{
36    let values: Vec<Value> = vaf
37        .value_assignment()
38        .distinct_values()
39        .into_iter()
40        .cloned()
41        .collect();
42
43    if values.len() > ENUMERATION_LIMIT {
44        return Err(Error::AudienceTooLarge {
45            values: values.len(),
46            limit: ENUMERATION_LIMIT,
47        });
48    }
49
50    for perm in permutations(&values) {
51        let audience = Audience::total(perm);
52        if vaf.accepted_for(&audience, arg)? {
53            return Ok(true);
54        }
55    }
56    Ok(false)
57}
58
59/// Returns `Ok(true)` iff `arg` is accepted by *every* total ordering of
60/// the values mentioned in the framework.
61pub fn objectively_accepted<A>(
62    vaf: &ValueBasedFramework<A>,
63    arg: &A,
64) -> Result<bool, Error>
65where
66    A: Clone + Eq + Hash + Ord + std::fmt::Debug,
67{
68    let values: Vec<Value> = vaf
69        .value_assignment()
70        .distinct_values()
71        .into_iter()
72        .cloned()
73        .collect();
74
75    if values.len() > ENUMERATION_LIMIT {
76        return Err(Error::AudienceTooLarge {
77            values: values.len(),
78            limit: ENUMERATION_LIMIT,
79        });
80    }
81
82    for perm in permutations(&values) {
83        let audience = Audience::total(perm);
84        if !vaf.accepted_for(&audience, arg)? {
85            return Ok(false);
86        }
87    }
88    Ok(true)
89}
90
91/// All permutations of a small slice. Heap's algorithm; allocates a
92/// fresh `Vec<Value>` per permutation. Acceptable up to ENUMERATION_LIMIT.
93fn permutations<T: Clone>(items: &[T]) -> Vec<Vec<T>> {
94    let mut result = Vec::new();
95    let mut working: Vec<T> = items.to_vec();
96    permute_recursive(&mut working, 0, &mut result);
97    result
98}
99
100fn permute_recursive<T: Clone>(items: &mut [T], start: usize, out: &mut Vec<Vec<T>>) {
101    if start == items.len() {
102        out.push(items.to_vec());
103        return;
104    }
105    for i in start..items.len() {
106        items.swap(start, i);
107        permute_recursive(items, start + 1, out);
108        items.swap(start, i);
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115    use crate::types::ValueAssignment;
116    use argumentation::ArgumentationFramework;
117
118    fn hal_carla() -> ValueBasedFramework<&'static str> {
119        let mut base = ArgumentationFramework::new();
120        for arg in ["h1", "c1", "h2", "c2"] {
121            base.add_argument(arg);
122        }
123        base.add_attack(&"h1", &"c1").unwrap();
124        base.add_attack(&"c1", &"h1").unwrap();
125        base.add_attack(&"c2", &"h2").unwrap();
126        base.add_attack(&"h2", &"c1").unwrap();
127
128        let mut values = ValueAssignment::new();
129        values.promote("h1", Value::new("life"));
130        values.promote("c1", Value::new("property"));
131        values.promote("h2", Value::new("fairness"));
132        values.promote("c2", Value::new("life"));
133
134        ValueBasedFramework::new(base, values)
135    }
136
137    #[test]
138    fn c2_objectively_accepted() {
139        let vaf = hal_carla();
140        // c2 has no in-edges in any audience → always grounded → always accepted.
141        assert!(objectively_accepted(&vaf, &"c2").unwrap());
142    }
143
144    #[test]
145    fn h1_subjectively_but_not_objectively_accepted() {
146        let vaf = hal_carla();
147        // h1 is accepted under [life > property, fairness, ...] but not
148        // under [property > life, ...].
149        assert!(subjectively_accepted(&vaf, &"h1").unwrap());
150        assert!(!objectively_accepted(&vaf, &"h1").unwrap());
151    }
152
153    #[test]
154    fn c1_subjectively_but_not_objectively_accepted() {
155        let vaf = hal_carla();
156        // Symmetric to h1.
157        assert!(subjectively_accepted(&vaf, &"c1").unwrap());
158        assert!(!objectively_accepted(&vaf, &"c1").unwrap());
159    }
160
161    #[test]
162    fn audience_too_large_returns_error() {
163        // Build a 7-value framework and verify the cap fires.
164        let mut base = ArgumentationFramework::new();
165        for arg in ["a", "b", "c", "d", "e", "f", "g"] {
166            base.add_argument(arg);
167        }
168        let mut values = ValueAssignment::new();
169        for (i, name) in ["v1", "v2", "v3", "v4", "v5", "v6", "v7"].iter().enumerate() {
170            let arg = ["a", "b", "c", "d", "e", "f", "g"][i];
171            values.promote(arg, Value::new(*name));
172        }
173        let vaf = ValueBasedFramework::new(base, values);
174        let result = subjectively_accepted(&vaf, &"a");
175        assert!(matches!(
176            result,
177            Err(Error::AudienceTooLarge { values: 7, limit: 6 })
178        ));
179    }
180
181    #[test]
182    fn permutations_of_three_yields_six() {
183        let perms = permutations(&[1, 2, 3]);
184        assert_eq!(perms.len(), 6);
185    }
186
187    #[test]
188    fn permutations_of_zero_yields_one_empty() {
189        let perms: Vec<Vec<i32>> = permutations(&[]);
190        assert_eq!(perms.len(), 1);
191        assert!(perms[0].is_empty());
192    }
193}