Spaces:
Sleeping
Sleeping
youssef
commited on
Commit
·
8bdd43d
1
Parent(s):
d02e800
reformatting
Browse files- aba/aba_framework.py +38 -31
- aba/exemples/atomic.txt +0 -1
- aba/exemples/td4_1.txt +0 -1
- aba/exemples/td4_2.txt +0 -1
- aba/exemples/td4_2and.txt +0 -1
aba/aba_framework.py
CHANGED
|
@@ -18,6 +18,7 @@ from .attacks import Attacks
|
|
| 18 |
from collections import deque, defaultdict
|
| 19 |
from itertools import combinations, product
|
| 20 |
|
|
|
|
| 21 |
class ABAFramework:
|
| 22 |
"""
|
| 23 |
Formal specification and implementation of Assumption-Based Argumentation (ABA) frameworks
|
|
@@ -36,7 +37,7 @@ class ABAFramework:
|
|
| 36 |
reverse_attacks (set[tuple]): Reverse ABA+ attacks due to preferences.
|
| 37 |
assumption_combinations (list[set[Literal]]): All subsets of base assumptions.
|
| 38 |
|
| 39 |
-
|
| 40 |
Mathematical foundation :
|
| 41 |
|
| 42 |
An ABA framework is a tuple ⟨L, R, A, C⟩ where:
|
|
@@ -74,10 +75,13 @@ class ABAFramework:
|
|
| 74 |
)
|
| 75 |
|
| 76 |
def __str__(self):
|
| 77 |
-
language_str = ', '.join(str(l)
|
|
|
|
| 78 |
rules_str = '\n'.join(str(r) for r in sorted(self.rules, key=str))
|
| 79 |
-
assumptions_str = ', '.join(str(a)
|
| 80 |
-
|
|
|
|
|
|
|
| 81 |
result = [
|
| 82 |
f"L = {{{language_str}}}",
|
| 83 |
f"R = {{\n{rules_str}\n}}",
|
|
@@ -85,14 +89,16 @@ class ABAFramework:
|
|
| 85 |
f"CONTRARIES = {{{contraries_str}}}"
|
| 86 |
]
|
| 87 |
if self.preferences:
|
| 88 |
-
sorted_prefs = sorted(self.preferences.items(),
|
|
|
|
| 89 |
preferences_str = '\n'.join(
|
| 90 |
f" {str(literal)} > {{{', '.join(str(p) for p in sorted(prefs, key=str))}}}"
|
| 91 |
for literal, prefs in sorted_prefs
|
| 92 |
)
|
| 93 |
result.append(f"PREFERENCES:\n{preferences_str}")
|
| 94 |
if self.arguments:
|
| 95 |
-
arguments_str = '\n'.join(str(arg)
|
|
|
|
| 96 |
result.append(f"ARGS:\n{arguments_str}")
|
| 97 |
return '\n'.join(result)
|
| 98 |
|
|
@@ -181,10 +187,8 @@ class ABAFramework:
|
|
| 181 |
if arg1.claim == contrary.contrary_attacker and contrary.contraried_literal in arg2.leaves:
|
| 182 |
self.attacks.add(Attacks(arg1, arg2))
|
| 183 |
|
| 184 |
-
|
| 185 |
-
|
| 186 |
# ------------------------- ABA Methods -------------------------
|
| 187 |
-
|
| 188 |
def transform_aba(self) -> None:
|
| 189 |
"""
|
| 190 |
Transforms the ABA framework to ensure it is both non-circular and atomic.
|
|
@@ -225,7 +229,7 @@ class ABAFramework:
|
|
| 225 |
L = {a, b, x}
|
| 226 |
A = {a, b}
|
| 227 |
R = { r1: a <- x }
|
| 228 |
-
|
| 229 |
After _make_aba_atomic():
|
| 230 |
L = {a, b, x, xd, xnd}
|
| 231 |
A = {a, b, xd, xnd}
|
|
@@ -278,7 +282,7 @@ class ABAFramework:
|
|
| 278 |
if rule.body and not all(lit in self.assumptions for lit in rule.body):
|
| 279 |
return False
|
| 280 |
return True
|
| 281 |
-
|
| 282 |
def is_aba_circular(self) -> bool:
|
| 283 |
"""
|
| 284 |
Checks if the ABA framework is circular by detecting cycles in the rule dependency graph.
|
|
@@ -330,7 +334,7 @@ class ABAFramework:
|
|
| 330 |
if has_cycle(lit, visited, set()):
|
| 331 |
return True # Cycle found
|
| 332 |
return False # No cycles
|
| 333 |
-
|
| 334 |
def make_aba_not_circular(self) -> None:
|
| 335 |
"""
|
| 336 |
Transforms the ABA framework to a non-circular one by renaming heads and bodies of rules.
|
|
@@ -364,7 +368,6 @@ class ABAFramework:
|
|
| 364 |
x1 <- a
|
| 365 |
x <- a
|
| 366 |
"""
|
| 367 |
-
|
| 368 |
|
| 369 |
k = len(self.language) - len(self.assumptions)
|
| 370 |
new_language = set(self.language)
|
|
@@ -392,10 +395,8 @@ class ABAFramework:
|
|
| 392 |
new_rule_name = f"{rule.rule_name}_{i+1}"
|
| 393 |
new_rules.add(Rule(new_rule_name, new_head, new_body))
|
| 394 |
|
| 395 |
-
|
| 396 |
self.language = new_language
|
| 397 |
self.rules = new_rules
|
| 398 |
-
|
| 399 |
|
| 400 |
# ------------------------- ABA+ Methods -------------------------
|
| 401 |
|
|
@@ -406,7 +407,7 @@ class ABAFramework:
|
|
| 406 |
ABA+ extends classical ABA by incorporating preference information over assumptions directly into
|
| 407 |
the attack relation, allowing for attack reversal when a target assumption is preferred over an
|
| 408 |
attacking assumption.
|
| 409 |
-
|
| 410 |
Procedure:
|
| 411 |
1. Ensures base_assumptions is set (preserves original assumptions before any transformation).
|
| 412 |
2. Generates all possible subsets of base assumptions (the power set).
|
|
@@ -424,15 +425,17 @@ class ABAFramework:
|
|
| 424 |
on the generated arguments to determine attacks between assumption sets.
|
| 425 |
"""
|
| 426 |
|
| 427 |
-
|
| 428 |
if not getattr(self, "base_assumptions", None):
|
| 429 |
self.base_assumptions = set(self.assumptions)
|
| 430 |
self.assumption_combinations = self.generate_assumption_combinations()
|
| 431 |
-
print(
|
| 432 |
-
|
|
|
|
| 433 |
self.generate_normal_reverse_attacks()
|
| 434 |
-
print(
|
| 435 |
-
|
|
|
|
|
|
|
| 436 |
|
| 437 |
def generate_assumption_combinations(self) -> list[set[Literal]]:
|
| 438 |
"""
|
|
@@ -462,7 +465,8 @@ class ABAFramework:
|
|
| 462 |
For n base assumptions, this generates 2^n combinations. The computational complexity
|
| 463 |
can become significant for large assumption sets.
|
| 464 |
"""
|
| 465 |
-
source_assumptions = getattr(
|
|
|
|
| 466 |
all_combos = []
|
| 467 |
for r in range(len(source_assumptions) + 1):
|
| 468 |
for combo in combinations(source_assumptions, r):
|
|
@@ -555,27 +559,30 @@ class ABAFramework:
|
|
| 555 |
|
| 556 |
for X in self.assumption_combinations:
|
| 557 |
for Y in self.assumption_combinations:
|
| 558 |
-
|
| 559 |
args_X = self.arguments_from_assumptions(X)
|
| 560 |
args_Y = self.arguments_from_assumptions(Y)
|
| 561 |
if not args_X or not args_Y:
|
| 562 |
continue
|
| 563 |
-
|
| 564 |
# Check for attacks from X to Y
|
| 565 |
for ax in args_X:
|
| 566 |
for ay in args_Y:
|
| 567 |
for contrary in self.contraries:
|
| 568 |
if ax.claim == contrary.contrary_attacker and contrary.contraried_literal in ay.leaves:
|
| 569 |
-
reverse = any(self.is_preferred(y, x)
|
|
|
|
| 570 |
if reverse:
|
| 571 |
-
self.reverse_attacks.add(
|
|
|
|
| 572 |
else:
|
| 573 |
-
self.normal_attacks.add(
|
| 574 |
-
|
|
|
|
| 575 |
# Now add the subset attacks: if (ab) attacks c, then (abc) should also attack c
|
| 576 |
additional_normal = set()
|
| 577 |
additional_reverse = set()
|
| 578 |
-
|
| 579 |
# For normal attacks: if X attacks Y, then any superset of X should attack Y
|
| 580 |
for X_att, Y_att in self.normal_attacks:
|
| 581 |
X = set(X_att)
|
|
@@ -583,7 +590,7 @@ class ABAFramework:
|
|
| 583 |
for Z in self.assumption_combinations:
|
| 584 |
if X.issubset(Z) and Z != X:
|
| 585 |
additional_normal.add((frozenset(Z), Y_att))
|
| 586 |
-
|
| 587 |
# For reverse attacks: if Y attacks X, then any superset of Y should attack X
|
| 588 |
for Y_att, X_att in self.reverse_attacks:
|
| 589 |
Y = set(Y_att)
|
|
@@ -591,7 +598,7 @@ class ABAFramework:
|
|
| 591 |
for Z in self.assumption_combinations:
|
| 592 |
if Y.issubset(Z) and Z != Y:
|
| 593 |
additional_reverse.add((frozenset(Z), X_att))
|
| 594 |
-
|
| 595 |
# Add the additional attacks
|
| 596 |
self.normal_attacks.update(additional_normal)
|
| 597 |
self.reverse_attacks.update(additional_reverse)
|
|
|
|
| 18 |
from collections import deque, defaultdict
|
| 19 |
from itertools import combinations, product
|
| 20 |
|
| 21 |
+
|
| 22 |
class ABAFramework:
|
| 23 |
"""
|
| 24 |
Formal specification and implementation of Assumption-Based Argumentation (ABA) frameworks
|
|
|
|
| 37 |
reverse_attacks (set[tuple]): Reverse ABA+ attacks due to preferences.
|
| 38 |
assumption_combinations (list[set[Literal]]): All subsets of base assumptions.
|
| 39 |
|
| 40 |
+
|
| 41 |
Mathematical foundation :
|
| 42 |
|
| 43 |
An ABA framework is a tuple ⟨L, R, A, C⟩ where:
|
|
|
|
| 75 |
)
|
| 76 |
|
| 77 |
def __str__(self):
|
| 78 |
+
language_str = ', '.join(str(l)
|
| 79 |
+
for l in sorted(self.language, key=str))
|
| 80 |
rules_str = '\n'.join(str(r) for r in sorted(self.rules, key=str))
|
| 81 |
+
assumptions_str = ', '.join(str(a)
|
| 82 |
+
for a in sorted(self.assumptions, key=str))
|
| 83 |
+
contraries_str = ', '.join(str(c)
|
| 84 |
+
for c in sorted(self.contraries, key=str))
|
| 85 |
result = [
|
| 86 |
f"L = {{{language_str}}}",
|
| 87 |
f"R = {{\n{rules_str}\n}}",
|
|
|
|
| 89 |
f"CONTRARIES = {{{contraries_str}}}"
|
| 90 |
]
|
| 91 |
if self.preferences:
|
| 92 |
+
sorted_prefs = sorted(self.preferences.items(),
|
| 93 |
+
key=lambda x: str(x[0]))
|
| 94 |
preferences_str = '\n'.join(
|
| 95 |
f" {str(literal)} > {{{', '.join(str(p) for p in sorted(prefs, key=str))}}}"
|
| 96 |
for literal, prefs in sorted_prefs
|
| 97 |
)
|
| 98 |
result.append(f"PREFERENCES:\n{preferences_str}")
|
| 99 |
if self.arguments:
|
| 100 |
+
arguments_str = '\n'.join(str(arg)
|
| 101 |
+
for arg in sorted(self.arguments, key=str))
|
| 102 |
result.append(f"ARGS:\n{arguments_str}")
|
| 103 |
return '\n'.join(result)
|
| 104 |
|
|
|
|
| 187 |
if arg1.claim == contrary.contrary_attacker and contrary.contraried_literal in arg2.leaves:
|
| 188 |
self.attacks.add(Attacks(arg1, arg2))
|
| 189 |
|
|
|
|
|
|
|
| 190 |
# ------------------------- ABA Methods -------------------------
|
| 191 |
+
|
| 192 |
def transform_aba(self) -> None:
|
| 193 |
"""
|
| 194 |
Transforms the ABA framework to ensure it is both non-circular and atomic.
|
|
|
|
| 229 |
L = {a, b, x}
|
| 230 |
A = {a, b}
|
| 231 |
R = { r1: a <- x }
|
| 232 |
+
|
| 233 |
After _make_aba_atomic():
|
| 234 |
L = {a, b, x, xd, xnd}
|
| 235 |
A = {a, b, xd, xnd}
|
|
|
|
| 282 |
if rule.body and not all(lit in self.assumptions for lit in rule.body):
|
| 283 |
return False
|
| 284 |
return True
|
| 285 |
+
|
| 286 |
def is_aba_circular(self) -> bool:
|
| 287 |
"""
|
| 288 |
Checks if the ABA framework is circular by detecting cycles in the rule dependency graph.
|
|
|
|
| 334 |
if has_cycle(lit, visited, set()):
|
| 335 |
return True # Cycle found
|
| 336 |
return False # No cycles
|
| 337 |
+
|
| 338 |
def make_aba_not_circular(self) -> None:
|
| 339 |
"""
|
| 340 |
Transforms the ABA framework to a non-circular one by renaming heads and bodies of rules.
|
|
|
|
| 368 |
x1 <- a
|
| 369 |
x <- a
|
| 370 |
"""
|
|
|
|
| 371 |
|
| 372 |
k = len(self.language) - len(self.assumptions)
|
| 373 |
new_language = set(self.language)
|
|
|
|
| 395 |
new_rule_name = f"{rule.rule_name}_{i+1}"
|
| 396 |
new_rules.add(Rule(new_rule_name, new_head, new_body))
|
| 397 |
|
|
|
|
| 398 |
self.language = new_language
|
| 399 |
self.rules = new_rules
|
|
|
|
| 400 |
|
| 401 |
# ------------------------- ABA+ Methods -------------------------
|
| 402 |
|
|
|
|
| 407 |
ABA+ extends classical ABA by incorporating preference information over assumptions directly into
|
| 408 |
the attack relation, allowing for attack reversal when a target assumption is preferred over an
|
| 409 |
attacking assumption.
|
| 410 |
+
|
| 411 |
Procedure:
|
| 412 |
1. Ensures base_assumptions is set (preserves original assumptions before any transformation).
|
| 413 |
2. Generates all possible subsets of base assumptions (the power set).
|
|
|
|
| 425 |
on the generated arguments to determine attacks between assumption sets.
|
| 426 |
"""
|
| 427 |
|
|
|
|
| 428 |
if not getattr(self, "base_assumptions", None):
|
| 429 |
self.base_assumptions = set(self.assumptions)
|
| 430 |
self.assumption_combinations = self.generate_assumption_combinations()
|
| 431 |
+
print(
|
| 432 |
+
f"Generated {len(self.assumption_combinations)} assumption combinations")
|
| 433 |
+
|
| 434 |
self.generate_normal_reverse_attacks()
|
| 435 |
+
print(
|
| 436 |
+
f"Generated {len(self.normal_attacks)} normal attacks (assumption sets)")
|
| 437 |
+
print(
|
| 438 |
+
f"Generated {len(self.reverse_attacks)} reverse attacks (assumption sets)")
|
| 439 |
|
| 440 |
def generate_assumption_combinations(self) -> list[set[Literal]]:
|
| 441 |
"""
|
|
|
|
| 465 |
For n base assumptions, this generates 2^n combinations. The computational complexity
|
| 466 |
can become significant for large assumption sets.
|
| 467 |
"""
|
| 468 |
+
source_assumptions = getattr(
|
| 469 |
+
self, "base_assumptions", self.assumptions)
|
| 470 |
all_combos = []
|
| 471 |
for r in range(len(source_assumptions) + 1):
|
| 472 |
for combo in combinations(source_assumptions, r):
|
|
|
|
| 559 |
|
| 560 |
for X in self.assumption_combinations:
|
| 561 |
for Y in self.assumption_combinations:
|
| 562 |
+
|
| 563 |
args_X = self.arguments_from_assumptions(X)
|
| 564 |
args_Y = self.arguments_from_assumptions(Y)
|
| 565 |
if not args_X or not args_Y:
|
| 566 |
continue
|
| 567 |
+
|
| 568 |
# Check for attacks from X to Y
|
| 569 |
for ax in args_X:
|
| 570 |
for ay in args_Y:
|
| 571 |
for contrary in self.contraries:
|
| 572 |
if ax.claim == contrary.contrary_attacker and contrary.contraried_literal in ay.leaves:
|
| 573 |
+
reverse = any(self.is_preferred(y, x)
|
| 574 |
+
for y in Y for x in X)
|
| 575 |
if reverse:
|
| 576 |
+
self.reverse_attacks.add(
|
| 577 |
+
(frozenset(Y), frozenset(X)))
|
| 578 |
else:
|
| 579 |
+
self.normal_attacks.add(
|
| 580 |
+
(frozenset(X), frozenset(Y)))
|
| 581 |
+
|
| 582 |
# Now add the subset attacks: if (ab) attacks c, then (abc) should also attack c
|
| 583 |
additional_normal = set()
|
| 584 |
additional_reverse = set()
|
| 585 |
+
|
| 586 |
# For normal attacks: if X attacks Y, then any superset of X should attack Y
|
| 587 |
for X_att, Y_att in self.normal_attacks:
|
| 588 |
X = set(X_att)
|
|
|
|
| 590 |
for Z in self.assumption_combinations:
|
| 591 |
if X.issubset(Z) and Z != X:
|
| 592 |
additional_normal.add((frozenset(Z), Y_att))
|
| 593 |
+
|
| 594 |
# For reverse attacks: if Y attacks X, then any superset of Y should attack X
|
| 595 |
for Y_att, X_att in self.reverse_attacks:
|
| 596 |
Y = set(Y_att)
|
|
|
|
| 598 |
for Z in self.assumption_combinations:
|
| 599 |
if Y.issubset(Z) and Z != Y:
|
| 600 |
additional_reverse.add((frozenset(Z), X_att))
|
| 601 |
+
|
| 602 |
# Add the additional attacks
|
| 603 |
self.normal_attacks.update(additional_normal)
|
| 604 |
self.reverse_attacks.update(additional_reverse)
|
aba/exemples/atomic.txt
CHANGED
|
@@ -1,4 +1,3 @@
|
|
| 1 |
-
|
| 2 |
L: [a,b,p,q,r]
|
| 3 |
A: [a,b]
|
| 4 |
C(a): r
|
|
|
|
|
|
|
| 1 |
L: [a,b,p,q,r]
|
| 2 |
A: [a,b]
|
| 3 |
C(a): r
|
aba/exemples/td4_1.txt
CHANGED
|
@@ -1,4 +1,3 @@
|
|
| 1 |
-
|
| 2 |
L: [a,b,l,s]
|
| 3 |
A: [a,b]
|
| 4 |
[r1]: l <- a
|
|
|
|
|
|
|
| 1 |
L: [a,b,l,s]
|
| 2 |
A: [a,b]
|
| 3 |
[r1]: l <- a
|
aba/exemples/td4_2.txt
CHANGED
|
@@ -1,4 +1,3 @@
|
|
| 1 |
-
|
| 2 |
L: [a,b,l,s,y]
|
| 3 |
A: [a,b,y]
|
| 4 |
C(a): s
|
|
|
|
|
|
|
| 1 |
L: [a,b,l,s,y]
|
| 2 |
A: [a,b,y]
|
| 3 |
C(a): s
|
aba/exemples/td4_2and.txt
CHANGED
|
@@ -1,4 +1,3 @@
|
|
| 1 |
-
|
| 2 |
L: [a,b,l,s,y]
|
| 3 |
A: [a,b,y]
|
| 4 |
C(a): s
|
|
|
|
|
|
|
| 1 |
L: [a,b,l,s,y]
|
| 2 |
A: [a,b,y]
|
| 3 |
C(a): s
|