1use crate::NQuin;
2
3pub const OP_DEFEASIBLE_OVERRIDE: u8 = 0x50;
4pub use crate::frame_layout::DEFEATER_BIT;
6
7#[derive(Debug, PartialEq, Clone, Copy)]
8pub enum DefeasibleStatus {
9 Strict, Overridden, Defeated, Active, }
14
15#[derive(Debug)]
16pub enum DefeasibleError {
17 BufferOverflow,
18}
19
20#[derive(Debug, Clone, Copy)]
21pub struct DefeasibleVerdict {
22 pub claim: NQuin,
23 pub status: DefeasibleStatus,
24}
25
26pub fn evaluate_defeasible_frame(
28 quins: &[NQuin],
29 context_hash: u64,
30 out: &mut [DefeasibleVerdict],
31) -> Result<usize, DefeasibleError> {
32 let mut count = 0;
33
34 for q in quins {
35 if context_hash != 0 && q.context != context_hash {
36 continue;
37 }
38
39 let is_defeater = (q.predicate & DEFEATER_BIT) != 0;
40 let opcode = (q.predicate & 0xFF) as u8;
41
42 let status = if is_defeater {
43 DefeasibleStatus::Overridden
44 } else if opcode == OP_DEFEASIBLE_OVERRIDE {
45 DefeasibleStatus::Defeated
46 } else {
47 DefeasibleStatus::Active
48 };
49
50 if count >= out.len() {
51 return Err(DefeasibleError::BufferOverflow);
52 }
53
54 out[count] = DefeasibleVerdict { claim: *q, status };
55 count += 1;
56 }
57
58 Ok(count)
59}
60
61pub fn holds_by_default(facts: &[NQuin], goal: &NQuin) -> bool {
68 !facts.iter().any(|q| {
69 q.subject == goal.subject && q.predicate == goal.predicate && q.object == goal.object
70 })
71}
72
73#[derive(Debug, Clone, Copy, PartialEq, Eq)]
83pub enum RuleKind {
84 Strict,
85 Defeasible,
86 Defeater,
87}
88
89#[derive(Debug, Clone, Copy)]
92pub struct DefeasibleRule {
93 pub id: u64,
94 pub kind: RuleKind,
95 pub literal: u64,
96 pub positive: bool,
97}
98
99#[derive(Debug, Clone, Copy, PartialEq, Eq)]
101pub enum AmbiguityMode {
102 Blocking,
104 Propagating,
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110pub enum Conclusion {
111 Positive,
112 Negative,
113 Ambiguous,
114 Undecided,
115}
116
117pub fn rules_conflict(a: &DefeasibleRule, b: &DefeasibleRule) -> bool {
119 a.literal == b.literal && a.positive != b.positive
120}
121
122pub fn is_superior(sup: &[(u64, u64)], a: u64, b: u64) -> bool {
125 sup.iter().any(|&(hi, lo)| hi == a && lo == b)
126}
127
128#[inline]
129fn polarity(r: &DefeasibleRule) -> Conclusion {
130 if r.positive {
131 Conclusion::Positive
132 } else {
133 Conclusion::Negative
134 }
135}
136
137#[inline]
140fn can_conclude(kind: RuleKind) -> bool {
141 matches!(kind, RuleKind::Strict | RuleKind::Defeasible)
142}
143
144pub fn resolve_conflict(
151 a: &DefeasibleRule,
152 b: &DefeasibleRule,
153 sup: &[(u64, u64)],
154 mode: AmbiguityMode,
155) -> Conclusion {
156 if !rules_conflict(a, b) {
157 return polarity(a);
158 }
159 match (a.kind, b.kind) {
161 (RuleKind::Strict, k) if k != RuleKind::Strict => return polarity(a),
162 (k, RuleKind::Strict) if k != RuleKind::Strict => return polarity(b),
163 _ => {}
164 }
165 if is_superior(sup, a.id, b.id) {
167 return if can_conclude(a.kind) {
168 polarity(a)
169 } else {
170 Conclusion::Undecided
171 };
172 }
173 if is_superior(sup, b.id, a.id) {
174 return if can_conclude(b.kind) {
175 polarity(b)
176 } else {
177 Conclusion::Undecided
178 };
179 }
180 match mode {
182 AmbiguityMode::Blocking => Conclusion::Undecided,
183 AmbiguityMode::Propagating => Conclusion::Ambiguous,
184 }
185}
186
187pub fn grounded_justified_rules(
203 rules: &[DefeasibleRule],
204 sup: &[(u64, u64)],
205) -> std::collections::HashSet<u64> {
206 use crate::modalities::argumentation::{Argument, ArgumentationFramework, Attack, AttackType};
207
208 let mut af = ArgumentationFramework::new();
209 for r in rules {
210 let concl = NQuin {
212 subject: r.literal,
213 predicate: r.positive as u64,
214 object: 0,
215 context: 0,
216 metadata: 0,
217 parity: 0,
218 };
219 af.add_argument(Argument::new(r.id, String::new(), Vec::new(), concl));
220 }
221 for (i, a) in rules.iter().enumerate() {
222 for b in &rules[i + 1..] {
223 if !rules_conflict(a, b) {
224 continue;
225 }
226 let a_strict = a.kind == RuleKind::Strict;
227 let b_strict = b.kind == RuleKind::Strict;
228 let a_sup = is_superior(sup, a.id, b.id);
229 let b_sup = is_superior(sup, b.id, a.id);
230 let a_attacks_b = !(b_strict && !a_strict) && !b_sup;
232 let b_attacks_a = !(a_strict && !b_strict) && !a_sup;
233 if a_attacks_b {
234 af.add_attack(Attack {
235 attacker: a.id,
236 target: b.id,
237 attack_type: AttackType::Rebuttal,
238 strength: 1.0,
239 });
240 }
241 if b_attacks_a {
242 af.add_attack(Attack {
243 attacker: b.id,
244 target: a.id,
245 attack_type: AttackType::Rebuttal,
246 strength: 1.0,
247 });
248 }
249 }
250 }
251 af.grounded_extension()
252 .into_iter()
253 .filter(|id| {
254 !rules
255 .iter()
256 .any(|r| r.id == *id && r.kind == RuleKind::Defeater)
257 })
258 .collect()
259}
260
261#[cfg(test)]
262mod tests {
263 use super::*;
264 use crate::q_hash;
265
266 fn rule(id: u64, kind: RuleKind, lit: u64, positive: bool) -> DefeasibleRule {
267 DefeasibleRule {
268 id,
269 kind,
270 literal: lit,
271 positive,
272 }
273 }
274
275 #[test]
276 fn strict_dominates_and_superiority_decides() {
277 let lit = q_hash("penguin:flies");
278 let r_pos = rule(1, RuleKind::Defeasible, lit, true); let r_neg = rule(2, RuleKind::Defeasible, lit, false); assert!(rules_conflict(&r_pos, &r_neg));
281
282 assert_eq!(
284 resolve_conflict(&r_pos, &r_neg, &[], AmbiguityMode::Blocking),
285 Conclusion::Undecided
286 );
287 assert_eq!(
288 resolve_conflict(&r_pos, &r_neg, &[], AmbiguityMode::Propagating),
289 Conclusion::Ambiguous
290 );
291
292 let sup = [(2u64, 1u64)];
294 assert_eq!(
295 resolve_conflict(&r_pos, &r_neg, &sup, AmbiguityMode::Blocking),
296 Conclusion::Negative
297 );
298
299 let r_strict = rule(3, RuleKind::Strict, lit, false);
301 assert_eq!(
302 resolve_conflict(&r_pos, &r_strict, &[], AmbiguityMode::Blocking),
303 Conclusion::Negative
304 );
305 }
306
307 #[test]
308 fn defeater_only_blocks_it_cannot_conclude() {
309 let lit = q_hash("claim:x");
310 let r = rule(1, RuleKind::Defeasible, lit, true);
311 let d = rule(2, RuleKind::Defeater, lit, false);
312 assert_eq!(
314 resolve_conflict(&r, &d, &[], AmbiguityMode::Blocking),
315 Conclusion::Undecided
316 );
317 let sup_d = [(2u64, 1u64)];
319 assert_eq!(
320 resolve_conflict(&r, &d, &sup_d, AmbiguityMode::Blocking),
321 Conclusion::Undecided
322 );
323 let sup_r = [(1u64, 2u64)];
325 assert_eq!(
326 resolve_conflict(&r, &d, &sup_r, AmbiguityMode::Blocking),
327 Conclusion::Positive
328 );
329 }
330
331 #[test]
332 fn non_conflicting_rules_just_conclude() {
333 let r1 = rule(1, RuleKind::Defeasible, q_hash("a"), true);
334 let r2 = rule(2, RuleKind::Defeasible, q_hash("b"), false);
335 assert!(!rules_conflict(&r1, &r2));
336 assert_eq!(
337 resolve_conflict(&r1, &r2, &[], AmbiguityMode::Blocking),
338 Conclusion::Positive
339 );
340 }
341
342 #[test]
343 fn grounded_extension_resolves_defeasible_conflict() {
344 let flies = q_hash("penguin:flies");
345 let r_bird = rule(1, RuleKind::Defeasible, flies, true); let r_peng = rule(2, RuleKind::Defeasible, flies, false); let none = grounded_justified_rules(&[r_bird, r_peng], &[]);
350 assert!(
351 !none.contains(&1) && !none.contains(&2),
352 "un-oriented conflict: neither justified"
353 );
354
355 let sup = [(2u64, 1u64)];
357 let g = grounded_justified_rules(&[r_bird, r_peng], &sup);
358 assert!(g.contains(&2) && !g.contains(&1));
359
360 let r_other = rule(3, RuleKind::Defeasible, q_hash("swims"), true);
362 let g2 = grounded_justified_rules(&[r_bird, r_peng, r_other], &sup);
363 assert!(g2.contains(&3));
364
365 let d = rule(4, RuleKind::Defeater, q_hash("claim"), false);
367 let r_c = rule(5, RuleKind::Defeasible, q_hash("claim"), true);
368 let gd = grounded_justified_rules(&[r_c, d], &[]);
369 assert!(!gd.contains(&4), "defeater never concludes");
370 }
371
372 #[test]
373 fn test_defeasible_evaluation() {
374 let mut out = Vec::with_capacity(10);
375 for _ in 0..10 {
376 out.push(DefeasibleVerdict {
377 claim: NQuin::default(),
378 status: DefeasibleStatus::Strict,
379 });
380 }
381
382 let ctx = q_hash("test_context");
383
384 let mut q_normal = NQuin::default();
385 q_normal.context = ctx;
386 q_normal.predicate = OP_DEFEASIBLE_OVERRIDE as u64;
387
388 let mut q_defeater = NQuin::default();
389 q_defeater.context = ctx;
390 q_defeater.predicate = DEFEATER_BIT | (OP_DEFEASIBLE_OVERRIDE as u64);
391
392 let quins = [q_normal, q_defeater];
393
394 let count = evaluate_defeasible_frame(&quins, ctx, &mut out).unwrap();
395 assert_eq!(count, 2);
396 assert_eq!(out[0].status, DefeasibleStatus::Defeated);
397 assert_eq!(out[1].status, DefeasibleStatus::Overridden);
398 }
399}