Demeude Edgar commited on
Commit
d02e800
·
1 Parent(s): a0e1005

aba and aba+ working

Browse files
aba/GOAL.txt DELETED
@@ -1,34 +0,0 @@
1
- Résultat attendu pour le example.txt en aba_plus
2
-
3
-
4
- ------- Normal Attacks (by assumption sets) -------
5
- {c,b,a} attacks {b}
6
- {c,a} attacks {c,b}
7
- {c,b,a} attacks {c,a}
8
- {c,b,a} attacks {c}
9
- {c,a} attacks {b,a}
10
- {c,a} attacks {c,a}
11
- {c,a} attacks {b}
12
- {c,b,a} attacks {c,b,a}
13
- {c,b,a} attacks {c,b}
14
- {c,a} attacks {c,b,a}
15
- {c,a} attacks {c}
16
- {c,a} attacks {b,a}
17
-
18
- ------- Reverse Attacks (by assumption sets) -------
19
- {c,a} attacks {c,b}
20
- {b,a} attacks {c, b, a}
21
- {b,a} attacks {c, b}
22
- {c,b,a} attacks {,b,ac}
23
- {a} attacks {c,b,c}
24
- {c,b,a} attacks {c,b}
25
- {a} attacks {c,b}
26
- {c,a} attacks {c,b,a}
27
-
28
-
29
- ---- Resultat atttendu -----
30
-
31
- Total combinations: 8
32
- a, ab, ac, abc, c, b, cb, ensemble_vide
33
-
34
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
aba/aba_framework.py CHANGED
@@ -17,12 +17,11 @@ from .rule import Rule
17
  from .attacks import Attacks
18
  from collections import deque, defaultdict
19
  from itertools import combinations, product
20
- # from pyvis.network import Network
21
-
22
 
23
  class ABAFramework:
24
  """
25
- Represents an Assumption-Based Argumentation (ABA) framework.
 
26
 
27
  Attributes:
28
  language (set[Literal]): The set of all literals in the framework.
@@ -36,6 +35,19 @@ class ABAFramework:
36
  normal_attacks (set[tuple]): Normal ABA+ attacks between assumption sets.
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
  def __init__(self, language: set[Literal], rules: set[Rule], assumptions: set[Literal],
@@ -62,7 +74,6 @@ class ABAFramework:
62
  )
63
 
64
  def __str__(self):
65
- """Return string representation of ABA framework, including preferences and arguments."""
66
  language_str = ', '.join(str(l) for l in sorted(self.language, key=str))
67
  rules_str = '\n'.join(str(r) for r in sorted(self.rules, key=str))
68
  assumptions_str = ', '.join(str(a) for a in sorted(self.assumptions, key=str))
@@ -89,14 +100,37 @@ class ABAFramework:
89
  return hash((frozenset(self.language), frozenset(self.rules),
90
  frozenset(self.assumptions), frozenset(self.contraries)))
91
 
92
- # ------------------------- Core ABA Methods -------------------------
93
 
94
  def is_preferred(self, lit1: Literal, lit2: Literal) -> bool:
95
- """Return True if lit1 is strictly preferred over lit2."""
 
 
 
 
 
 
96
  return lit1 in self.preferences and lit2 in self.preferences[lit1]
97
 
98
  def generate_arguments(self) -> None:
99
- """Generate all possible arguments based on rules and assumptions."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  arg_count = 1
101
  arguments_by_claim = defaultdict(set)
102
  queue = deque()
@@ -136,21 +170,298 @@ class ABAFramework:
136
  self.arguments = set().union(*arguments_by_claim.values())
137
 
138
  def generate_attacks(self) -> None:
139
- """Generate classical attacks between arguments."""
 
 
 
 
140
  for arg1 in self.arguments:
141
  for arg2 in self.arguments:
142
  for contrary in self.contraries:
143
  if arg1.claim == contrary.contrary_attacker and contrary.contraried_literal in arg2.leaves:
144
  self.attacks.add(Attacks(arg1, arg2))
145
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
146
  # ------------------------- ABA+ Methods -------------------------
147
 
148
- def save_base_assumptions(self):
149
- """Save current assumptions as base_assumptions (before atomic transformation)."""
150
- self.base_assumptions = set(self.assumptions)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
151
 
152
- def _generate_assumption_combinations(self) -> list[set[Literal]]:
153
- """Generate all subsets of base assumptions (including empty set)."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  source_assumptions = getattr(self, "base_assumptions", self.assumptions)
155
  all_combos = []
156
  for r in range(len(source_assumptions) + 1):
@@ -159,21 +470,88 @@ class ABAFramework:
159
  return all_combos
160
 
161
  def arguments_from_assumptions(self, S: set[Literal]) -> set[Argument]:
162
- """Return arguments whose leaves are subsets of S."""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163
  return {arg for arg in self.arguments if arg.leaves.issubset(S) or (not arg.leaves and set() <= S)}
164
 
165
  def generate_normal_reverse_attacks(self) -> None:
166
  """
167
- Generate ABA+ attacks between assumption sets.
168
-
169
- - Normal: X -> Y if there exists ax from X attacking ay from Y
170
- without preference-based reversal.
171
- - Reverse: Y -> X if SOME y in Y is strictly preferred over SOME x in X.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
172
  """
173
  self.normal_attacks.clear()
174
  self.reverse_attacks.clear()
175
  if not self.assumption_combinations:
176
- self.assumption_combinations = self._generate_assumption_combinations()
177
 
178
  for X in self.assumption_combinations:
179
  for Y in self.assumption_combinations:
@@ -218,51 +596,6 @@ class ABAFramework:
218
  self.normal_attacks.update(additional_normal)
219
  self.reverse_attacks.update(additional_reverse)
220
 
221
-
222
- def make_aba_plus(self) -> None:
223
- """Generate ABA+ framework: assumption combinations and ABA+ attacks."""
224
- if not getattr(self, "base_assumptions", None):
225
- self.base_assumptions = set(self.assumptions)
226
- self.assumption_combinations = self._generate_assumption_combinations()
227
- print(f"Generated {len(self.assumption_combinations)} assumption combinations")
228
- self.generate_normal_reverse_attacks()
229
- print(f"Generated {len(self.normal_attacks)} normal attacks (assumption sets)")
230
- print(f"Generated {len(self.reverse_attacks)} reverse attacks (assumption sets)")
231
-
232
- # # ------------------------- Visualization -------------------------
233
-
234
- # def plot_aba_plus_graph(self, output_html="aba_plus_graph.html"):
235
- # """
236
- # Visualize ABA+ framework with PyVis.
237
- # - Normal attacks: solid black lines
238
- # - Reverse attacks: dashed red lines
239
- # Nodes: labeled with assumption sets
240
- # """
241
- # if not hasattr(self, "normal_attacks") or not hasattr(self, "reverse_attacks"):
242
- # print("Warning: ABA+ attacks not generated. Call make_aba_plus() first.")
243
- # return
244
-
245
- # net = Network(directed=True, notebook=False, height="750px", width="100%")
246
- # net.set_options("""
247
- # {
248
- # "physics": {"enabled": true, "stabilization": {"enabled": true, "iterations": 200}}
249
- # }
250
- # """)
251
-
252
- # # Add nodes labeled by assumption sets
253
- # for S in self.assumption_combinations:
254
- # label = "{" + ",".join(str(l) for l in sorted(S, key=str)) + "}" if S else "{}"
255
- # net.add_node(str(frozenset(S)), label=label)
256
-
257
- # # Normal attacks
258
- # for att in self.normal_attacks:
259
- # net.add_edge(str(att[0]), str(att[1]), color='black', dashes=False, width=2, title="Normal Attack")
260
-
261
- # # Reverse attacks
262
- # for att in self.reverse_attacks:
263
- # net.add_edge(str(att[0]), str(att[1]), color='red', dashes=True, width=2, title="Reverse Attack")
264
-
265
- # net.write_html(output_html)
266
- # print(f"ABA+ attack graph saved to {output_html}")
267
- # print(f" - Normal attacks: {len(self.normal_attacks)}")
268
- # print(f" - Reverse attacks: {len(self.reverse_attacks)}")
 
17
  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
24
+ with support for preference-based extensions (ABA+).
25
 
26
  Attributes:
27
  language (set[Literal]): The set of all literals in the framework.
 
35
  normal_attacks (set[tuple]): Normal ABA+ attacks between assumption sets.
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:
43
+ L ⊆ Lit: The language; a finite set of literals
44
+ R ⊆ Rules: The set of rules of the form h ← b₁, ..., bₙ where h ∈ L and bᵢ ∈ L
45
+ A ⊆ L: The set of assumptions; A and (Lit \ L) partition the language
46
+ C ⊆ A × L: The contrary relation; for each assumption a ∈ A, C(a) is its contrary
47
+
48
+ An ABA+ framework extends ABA by adding:
49
+ P ⊆ A × A: A preference relation over assumptions (typically antisymmetric)
50
+
51
  """
52
 
53
  def __init__(self, language: set[Literal], rules: set[Rule], assumptions: set[Literal],
 
74
  )
75
 
76
  def __str__(self):
 
77
  language_str = ', '.join(str(l) for l in sorted(self.language, key=str))
78
  rules_str = '\n'.join(str(r) for r in sorted(self.rules, key=str))
79
  assumptions_str = ', '.join(str(a) for a in sorted(self.assumptions, key=str))
 
100
  return hash((frozenset(self.language), frozenset(self.rules),
101
  frozenset(self.assumptions), frozenset(self.contraries)))
102
 
103
+ # ------------------------- Core Methods -------------------------
104
 
105
  def is_preferred(self, lit1: Literal, lit2: Literal) -> bool:
106
+ """
107
+ Determines whether the first literal is strictly preferred over the second one.
108
+
109
+ Returns:
110
+ bool: True if lit1 is strictly preferred over lit2 according to the framework's
111
+ preference relation (i.e., lit1 > lit2). False otherwise.
112
+ """
113
  return lit1 in self.preferences and lit2 in self.preferences[lit1]
114
 
115
  def generate_arguments(self) -> None:
116
+ """
117
+ Generates all possible arguments in the ABA framework based on the rules, assumptions, and contraries.
118
+
119
+ This method populates the self.arguments set with all arguments that can be constructed from the framework.
120
+
121
+ Example:
122
+ Given:
123
+ L = {a, b, x, y}
124
+ A = {a, b}
125
+ R = { r1: x ← a, b r2: y ← x r3: z ← }
126
+
127
+ Arguments generated:
128
+ A1: ⟨{a} ↦ a⟩
129
+ A2: ⟨{b} ↦ b⟩
130
+ A3: ⟨∅ ↦ z⟩
131
+ A4: ⟨{a, b} ↦ x⟩ (via r1, using A1 and A2)
132
+ A5: ⟨{a, b} ↦ y⟩ (via r2, using A4)
133
+ """
134
  arg_count = 1
135
  arguments_by_claim = defaultdict(set)
136
  queue = deque()
 
170
  self.arguments = set().union(*arguments_by_claim.values())
171
 
172
  def generate_attacks(self) -> None:
173
+ """
174
+ Generates all possible attacks between arguments based on the contraries in the ABA framework.
175
+
176
+ This method populates the self.attacks set with all attacks that can be constructed from the framework.
177
+ """
178
  for arg1 in self.arguments:
179
  for arg2 in self.arguments:
180
  for contrary in self.contraries:
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.
191
+
192
+ Procedure:
193
+ 1. Checks if the framework is circular using is_aba_circular().
194
+ 2. If it is circular, calls _make_aba_not_circular() to remove circularity.
195
+ 3. If it is not circular but not atomic, calls _make_aba_atomic() to ensure atomicity.
196
+ 4. The transformation is performed in-place and modifies the framework's rules and language as needed.
197
+
198
+ After calling this function, the ABA framework will be non-circular and atomic.
199
+ """
200
+ print("\n ------- Transforming ABA framework -------\n")
201
+ if self.is_aba_circular():
202
+ print("The ABA Framework is circular\n")
203
+ self.make_aba_not_circular()
204
+ elif not self.is_aba_atomic():
205
+ print("The ABA Framework is not atomic\n")
206
+ self.make_aba_atomic()
207
+
208
+ def make_aba_atomic(self) -> None:
209
+ """
210
+ Transforms the ABA framework into an atomic one.
211
+
212
+ Procedure:
213
+ 1. For each literal x in the language that is not an assumption:
214
+ - Introduce two new literals: xd and xnd (both are assumptions).
215
+ - Add both to the language and assumptions.
216
+ 2. For each rule:
217
+ - Replace non-assumption literals in the body with their 'xd' counterparts.
218
+ 3. For each new pair (xd, xnd):
219
+ - Add contraries: Contrary(xd, xnd) and Contrary(xnd, x).
220
+
221
+ After this transformation, all rule bodies contain only assumptions.
222
+
223
+ Example:
224
+ Original framework:
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}
232
+ R = { a <- xd }
233
+ Contraries = { xd̄ = xnd, xnd̄ = x }
234
+ """
235
+ new_language = set(self.language)
236
+ new_assumptions = set(self.assumptions)
237
+ new_rules = set()
238
+ new_contraries = set(self.contraries)
239
+
240
+ # Step 1: Create xd and xnd for each non-assumption literal
241
+ mapping = {} # maps original non-assumption literal -> xd
242
+ for lit in self.language:
243
+ if lit not in self.assumptions:
244
+ xd = Literal(f"{lit}d")
245
+ xnd = Literal(f"{lit}nd")
246
+ new_language.update({xd, xnd})
247
+ new_assumptions.update({xd, xnd})
248
+ mapping[lit] = xd
249
+ # Add contraries
250
+ new_contraries.add(Contrary(xd, xnd))
251
+ new_contraries.add(Contrary(xnd, lit))
252
+
253
+ # Step 2: Replace non-assumptions in rule bodies with xd
254
+ for rule in self.rules:
255
+ new_body = set()
256
+ for lit in rule.body:
257
+ if lit in mapping: # replace non-assumption with xd
258
+ new_body.add(mapping[lit])
259
+ else:
260
+ new_body.add(lit)
261
+ new_rules.add(Rule(rule.rule_name, rule.head, new_body))
262
+
263
+ # Step 3: Update framework
264
+ self.language = new_language
265
+ self.assumptions = new_assumptions
266
+ self.rules = new_rules
267
+ self.contraries = new_contraries
268
+
269
+ def is_aba_atomic(self) -> bool:
270
+ """
271
+ Checks if the ABA framework is atomic.
272
+
273
+ An ABA framework is atomic if every rule's body (if non-empty) consists only of assumptions.
274
+ Returns:
275
+ bool: True if the framework is atomic, False otherwise.
276
+ """
277
+ for rule in self.rules:
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.
285
+
286
+ Returns:
287
+ bool: True if the framework is circular (i.e., contains a cycle), False otherwise.
288
+
289
+ Procedure:
290
+ - The dependency graph is constructed where each node is a literal.
291
+ - For each rule, an edge is added from every literal in the rule's body to the rule's head.
292
+ - A cycle in this graph means there is a sequence of rules such that a literal can be derived from itself,
293
+ directly or indirectly, which is the definition of circularity in ABA frameworks.
294
+ - The function uses depth-first search (DFS) to detect cycles in the graph.
295
+ """
296
+ # Build adjacency list: for each literal, store the set of literals it can reach via rules
297
+ adj = {lit: set() for lit in self.language}
298
+ for rule in self.rules:
299
+ for body_lit in rule.body:
300
+ adj[body_lit].add(rule.head)
301
+
302
+ def has_cycle(lit, visited, stack):
303
+ """
304
+ Helper function to perform DFS and detect cycles.
305
+
306
+ Args:
307
+ lit: The current literal being visited.
308
+ visited: Set of literals that have been fully explored.
309
+ stack: Set of literals in the current DFS path (recursion stack).
310
+
311
+ Returns:
312
+ True if a cycle is detected starting from 'lit', False otherwise.
313
+ """
314
+ visited.add(lit)
315
+ stack.add(lit)
316
+ for neighbor in adj.get(lit, []):
317
+ if neighbor not in visited:
318
+ if has_cycle(neighbor, visited, stack):
319
+ return True
320
+ elif neighbor in stack:
321
+ # Found a back edge, which means a cycle exists
322
+ return True
323
+ stack.remove(lit)
324
+ return False
325
+
326
+ visited = set()
327
+ # Check for cycles starting from each literal in the language
328
+ for lit in self.language:
329
+ if lit not in visited:
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.
337
+
338
+ Procedure:
339
+ 1. Compute k = |language| - |assumptions|.
340
+ 2. For each atomic rule (body is empty or only contains assumptions):
341
+ - Create new rules with heads renamed as x1, x2, ..., x(k-1) with the same body.
342
+ - Keep the original atomic rule.
343
+ 3. For each non-atomic rule (body contains non-assumptions):
344
+ - Create k-1 new rules with heads renamed as x2, x3, ... and bodies renamed by iteration index.
345
+ - In the last iteration (i = k-1), keep the original head but update the body with the last renamed literals.
346
+ 4. Update the framework's language and rules with the new transformed rules.
347
+
348
+ After this transformation, circular dependencies in arguments are eliminated.
349
+ The function modifies the ABAFramework in-place and does not return any value.
350
+
351
+ Example:
352
+ Original framework:
353
+ Language: {a, b, x, y}
354
+ Assumptions: {a, b}
355
+ Rules:
356
+ r1: y <- y
357
+ r2: x <- x
358
+ r3: x <- a
359
+
360
+ After _make_aba_not_circular():
361
+ New rules:
362
+ y <- y1
363
+ x <- x1
364
+ x1 <- a
365
+ x <- a
366
+ """
367
+
368
+
369
+ k = len(self.language) - len(self.assumptions)
370
+ new_language = set(self.language)
371
+ new_rules = set()
372
+
373
+ for rule in self.rules:
374
+ if not rule.body or all(lit in self.assumptions for lit in rule.body):
375
+ # Atomic rule: create x1, x2, ..., x(k-1)
376
+ for i in range(1, k):
377
+ new_head = Literal(f"{rule.head}{i}")
378
+ new_body = set(rule.body) # <<< fix: define new_body here
379
+ new_language.add(new_head)
380
+ new_rule_name = f"{rule.rule_name}_{i+1}" # unique name
381
+ new_rules.add(Rule(new_rule_name, new_head, new_body))
382
+ new_rules.add(rule) # Keep original
383
+
384
+ else:
385
+ # Non-atomic: rename head and body
386
+ for i in range(1, k):
387
+ if i < k - 1:
388
+ new_head = Literal(f"{rule.head}{i+1}")
389
+ else:
390
+ new_head = rule.head # last iteration keeps original head
391
+ new_body = {Literal(f"{lit}{i}") for lit in rule.body}
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
 
402
+ def make_aba_plus(self) -> None:
403
+ """
404
+ Generates the ABA+ framework by creating all assumption set combinations and computing attacks.
405
+
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).
413
+ 3. Computes both normal attacks and reverse attacks between assumption sets based on:
414
+ - Classical ABA attack rules (arguments attacking assumptions)
415
+ - Preference-based attack reversal (when target assumptions are preferred)
416
+
417
+ After calling this function:
418
+ - self.assumption_combinations contains all subsets of base assumptions
419
+ - self.normal_attacks contains standard attacks between assumption sets
420
+ - self.reverse_attacks contains preference-reversed attacks between assumption sets
421
+
422
+ Note:
423
+ This method should be called after generate_arguments() has been executed, as it relies
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(f"Generated {len(self.assumption_combinations)} assumption combinations")
432
+
433
+ self.generate_normal_reverse_attacks()
434
+ print(f"Generated {len(self.normal_attacks)} normal attacks (assumption sets)")
435
+ print(f"Generated {len(self.reverse_attacks)} reverse attacks (assumption sets)")
436
+
437
+ def generate_assumption_combinations(self) -> list[set[Literal]]:
438
+ """
439
+ Generates all possible subsets of base assumptions (the power set).
440
+
441
+ This method creates every possible combination of assumptions, from the empty set to the complete
442
+ set of all base assumptions. Each combination represents a potential stance or position in the
443
+ argumentation framework.
444
+
445
+ Returns:
446
+ list[set[Literal]]: A list where each element is a set of literals representing one possible
447
+ combination of assumptions. The list includes:
448
+ - The empty set ∅
449
+ - All singleton sets {a} for each assumption a
450
+ - All pairs, triples, etc., up to the full set of assumptions
451
+
452
+ Example:
453
+ If base_assumptions = {a, b}, the result will be:
454
+ [
455
+ set(), # empty set
456
+ {a}, # singleton
457
+ {b}, # singleton
458
+ {a, b} # complete set
459
+ ]
460
+
461
+ Note:
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(self, "base_assumptions", self.assumptions)
466
  all_combos = []
467
  for r in range(len(source_assumptions) + 1):
 
470
  return all_combos
471
 
472
  def arguments_from_assumptions(self, S: set[Literal]) -> set[Argument]:
473
+ """
474
+ Returns all arguments that can be constructed using only assumptions from set S.
475
+
476
+ An argument is valid for assumption set S if all its supporting assumptions (leaves) are
477
+ contained within S. This is crucial for determining which arguments are available when
478
+ reasoning from a particular set of assumptions.
479
+
480
+ Args:
481
+ S (set[Literal]): A set of assumptions to check against.
482
+
483
+ Returns:
484
+ set[Argument]: The set of all arguments whose leaves (supporting assumptions) are
485
+ subsets of S. This includes:
486
+ - Arguments with leaves ⊆ S
487
+ - Arguments with no leaves (derived from rules with empty bodies)
488
+
489
+ Example:
490
+ Given:
491
+ - Argument A1 with leaves {a, b}
492
+ - Argument A2 with leaves {a}
493
+ - Argument A3 with leaves {c}
494
+ - S = {a, b}
495
+
496
+ Returns: {A1, A2} (A3 is excluded because c ∉ S)
497
+ """
498
  return {arg for arg in self.arguments if arg.leaves.issubset(S) or (not arg.leaves and set() <= S)}
499
 
500
  def generate_normal_reverse_attacks(self) -> None:
501
  """
502
+ Generates both normal and reverse attacks between assumption sets in ABA+.
503
+
504
+ This is the core method for implementing preference-based attack reversal in ABA+. It examines
505
+ all pairs of assumption sets and determines whether attacks exist and whether they should be
506
+ reversed based on preference information.
507
+
508
+ Attack Types:
509
+ 1. Normal Attack (X attacks Y):
510
+ - There exists an argument from X that attacks an argument from Y
511
+ - The attack is based on contraries (classical ABA attack)
512
+ - No preference reversal occurs (i.e., no assumption in Y is preferred over assumptions in X)
513
+
514
+ 2. Reverse Attack (Y attacks X):
515
+ - There would normally be an attack from X to Y
516
+ - BUT some assumption y ∈ Y is strictly preferred over some assumption x ∈ X
517
+ - The preference relationship reverses the direction of the attack
518
+
519
+ Procedure:
520
+ 1. Clears any existing normal and reverse attacks.
521
+ 2. Generates assumption combinations if not already done.
522
+ 3. For each pair of assumption sets (X, Y):
523
+ a. Gets all arguments constructible from X and Y respectively.
524
+ b. Checks if any argument from X attacks any argument from Y (via contraries).
525
+ c. If an attack exists:
526
+ - Checks if any assumption in Y is preferred over any assumption in X.
527
+ - If yes → creates a reverse attack (Y attacks X).
528
+ - If no → creates a normal attack (X attacks Y).
529
+ 4. Adds subset closure attacks:
530
+ - If X attacks Y normally, then any superset of X also attacks Y.
531
+ - If Y attacks X in reverse, then any superset of Y also attacks X.
532
+
533
+ Mathematical Definition:
534
+ - Normal: X ↝ Y iff ∃ax ∈ Args(X), ∃ay ∈ Args(Y): ax attacks ay ∧ ¬(∃y∈Y, ∃x∈X: y > x)
535
+ - Reverse: Y ↝ X iff ∃ax ∈ Args(X), ∃ay ∈ Args(Y): ax attacks ay ∧ (∃y∈Y, ∃x∈X: y > x)
536
+
537
+ Side Effects:
538
+ Populates self.normal_attacks and self.reverse_attacks with frozenset tuples representing
539
+ the attack relationships between assumption sets.
540
+
541
+ Example:
542
+ Given:
543
+ - Assumptions: {a, b, c}
544
+ - Preferences: b > a (b is preferred over a)
545
+ - Argument from {a} attacks argument from {b}
546
+
547
+ Result:
548
+ - Without preferences: {a} ↝ {b} (normal attack)
549
+ - With preferences: {b} ↝ {a} (reverse attack, because b > a)
550
  """
551
  self.normal_attacks.clear()
552
  self.reverse_attacks.clear()
553
  if not self.assumption_combinations:
554
+ self.assumption_combinations = self.generate_assumption_combinations()
555
 
556
  for X in self.assumption_combinations:
557
  for Y in self.assumption_combinations:
 
596
  self.normal_attacks.update(additional_normal)
597
  self.reverse_attacks.update(additional_reverse)
598
 
599
+ def arguments_from_assumptions(self, S: set[Literal]) -> set[Argument]:
600
+ """Return arguments whose leaves are subsets of S."""
601
+ return {arg for arg in self.arguments if arg.leaves.issubset(S) or (not arg.leaves and set() <= S)}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
aba/exemples/{example.txt → exemple.txt} RENAMED
File without changes
app.py CHANGED
@@ -106,6 +106,23 @@ async def aba_upload(file: UploadFile = File(...)):
106
  content = await file.read()
107
  text = content.decode("utf-8")
108
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
109
  aba_framework = build_aba_framework_from_text(text)
110
  aba_framework = prepare_aba_plus_framework(aba_framework)
111
  aba_framework.make_aba_plus()
 
106
  content = await file.read()
107
  text = content.decode("utf-8")
108
 
109
+ aba_framework = build_aba_framework_from_text(text)
110
+ aba_framework.generate_arguments()
111
+ aba_framework.generate_attacks()
112
+
113
+ results = {
114
+ "assumptions": [str(a) for a in aba_framework.assumptions],
115
+ "arguments": [str(arg) for arg in aba_framework.arguments],
116
+ "attacks": [str(att) for att in aba_framework.attacks],
117
+ }
118
+ return results
119
+
120
+
121
+ @app.post("/aba-plus-upload")
122
+ async def aba_upload(file: UploadFile = File(...)):
123
+ content = await file.read()
124
+ text = content.decode("utf-8")
125
+
126
  aba_framework = build_aba_framework_from_text(text)
127
  aba_framework = prepare_aba_plus_framework(aba_framework)
128
  aba_framework.make_aba_plus()