#r "nuget: IKVM, 8.15.0"
#r "nuget: IKVM.Image, 8.15.0"
- IKVM, 8.15.0
- IKVM.Image, 8.15.0
Série Tweety — port C#/.NET natif (EPIC #4667). Ce notebook exploite le module
arg-dungde TweetyProject sans JVM : la librairie Java est compilée vers un fat-jar Maven shade puis exécutée sur le runtime .NET via IKVM.
Navigation : Tweety-2-Basic-Logics-Csharp (propositionnel) · Tweety-2b-Semantics-Csharp (mondes possibles) · Tweety-2c-FOL-Csharp (premier ordre) · Tweety-3-Dung (ce notebook).
L’argumentation abstraite de Dung (1995) est le formalisme historique fondateur de TweetyProject. On s’affranchit de la nature des arguments pour ne retenir que leur structure d’attaque :
a → b (« a réfute b ») est une arête orientée ;La question centrale : étant donné un AF, quels arguments accepter ? Dung définit plusieurs sémantiques (ensembles d’arguments « rationnellement acceptables » = les extensions) :
| Sémantique | Idée | Extensions |
|---|---|---|
| Sans conflit (CF) | pas d’attaque mutuelle interne | plusieurs |
| Admissible | sans conflit + se défend contre toute attaque | plusieurs |
| Complète (CO) | admissible + contient tout argument qu’elle défend | plusieurs |
| Fondée (GR, grounded) | la plus petite extension complète | une seule |
| Préférée (PR) | une extension complète maximale (pour l’inclusion) | ≥ 1 |
| Stable (ST) | bat (attaque) tout argument hors de l’extension | 0 ou plusieurs |
Ce notebook construit des AF en C#, calcule leurs extensions sous chaque sémantique, et teste l’acceptation (sceptique) d’un argument.
arg-dungOn installe le runtime IKVM, on fusionne l’image (base + arch), puis on charge la DLL org.tweetyproject.tweety-dung.dll (compilée côté build à partir d’un fat-jar shade embarquant arg-dung + ses dépendances transitives : graphs, commons, math, logics-fol, logics-pl).
using System.IO;
// RID de la machine courante (win-x64, linux-x64, osx-arm64...) : IKVM.Image tire l'image native de chaque plateforme.
string ikvmVer = "8.15.0", ikvmRid = (OperatingSystem.IsWindows() ? "win" : OperatingSystem.IsMacOS() ? "osx" : "linux") + "-" + System.Runtime.InteropServices.RuntimeInformation.ProcessArchitecture.ToString().ToLowerInvariant();
string nugetRoot = Environment.GetEnvironmentVariable("NUGET_PACKAGES")
?? Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".nuget", "packages");
string ikvmBaseAny = Path.Combine(nugetRoot, "ikvm.image", ikvmVer, "ikvm", "any", "any");
string ikvmArchDir = Path.Combine(nugetRoot, "ikvm.image.runtime." + ikvmRid, ikvmVer, "ikvm", "any", ikvmRid);
string ikvmHome = Path.Combine(Path.GetTempPath(), "ikvm-home-" + ikvmVer + "-" + ikvmRid);
void IkvmCopyMerge(string src, string dst)
{
foreach (var d in Directory.GetDirectories(src, "*", SearchOption.AllDirectories))
Directory.CreateDirectory(d.Replace(src, dst));
foreach (var f in Directory.GetFiles(src, "*", SearchOption.AllDirectories))
{
var t = f.Replace(src, dst);
Directory.CreateDirectory(Path.GetDirectoryName(t));
File.Copy(f, t, overwrite: true);
}
}
if (Directory.Exists(ikvmBaseAny) && Directory.Exists(ikvmArchDir))
{
Directory.CreateDirectory(ikvmHome);
IkvmCopyMerge(ikvmBaseAny, ikvmHome);
IkvmCopyMerge(ikvmArchDir, ikvmHome);
}
AppContext.SetData("IKVM.Home", ikvmHome);
Console.WriteLine("IKVM home=" + (File.Exists(Path.Combine(ikvmHome, "lib", "tzdb.dat")) ? "OK" : "MISSING"));
IKVM home=OK
// Constat 3 (#5039) : la directive #r est silencieuse par defaut en .NET Interactive.
// On verifie que la DLL IKVM/Tweety referencee est bien presente et lisible (metadata).
using System.Reflection;
var tweetyDll = "org.tweetyproject.tweety-dung.dll";
var an = AssemblyName.GetAssemblyName(tweetyDll);
Console.WriteLine($"Tweety (IKVM) reference chargee : {an.Name} v{an.Version} ({new FileInfo(tweetyDll).Length / 1024 / 1024:F1} Mo).");Tweety (IKVM) reference chargee : org.tweetyproject.tweety-dung v1.30.0.0 (8.0 Mo).
Un AF se construit avec DungTheory (qui implémente Graph<Argument>). On y ajoute des arguments (Argument, nommés) et des attaques (Attack(attacker, attacked)).
a → bL’argument a attaque b. Intuitivement, si on accepte a, on ne peut pas accepter b.
using org.tweetyproject.arg.dung.syntax;
using org.tweetyproject.arg.dung.semantics;
using org.tweetyproject.arg.dung.reasoner;
using System.Collections.Generic;
var a = new Argument("a");
var b = new Argument("b");
var af = new DungTheory();
af.add(a);
af.add(b);
af.add(new Attack(a, b)); // a attaque b
Console.WriteLine("AF = " + af);
Console.WriteLine("nb noeuds = " + af.getNodes().size());
AF = <{ a, b },[(a,b)]>
nb noeuds = 2
Quand a et b s’attaquent mutuellement, aucune des deux sémantiques « strictes » ne peut trancher seule : c’est le cas typique où plusieurs extensions coexistent (l’une accepte a, l’autre b). On construit aussi un cycle c → d → e → c.
var af2 = new DungTheory();
var x = new Argument("x");
var y = new Argument("y");
af2.add(x); af2.add(y);
af2.add(new Attack(x, y));
af2.add(new Attack(y, x)); // attaque mutuelle
Console.WriteLine("AF2 (attaque mutuelle) = " + af2);
Console.WriteLine("Bien-fondé ? " + af2.isWellFounded()); // faux : cycle d'attaque
AF2 (attaque mutuelle) = <{ x, y },[(x,y), (y,x)]>
Bien-fondé ? False
Chaque sémantique est incarnée par un raisonneur (Simple*Reasoner). Tous héritent de AbstractExtensionReasoner qui expose :
getModels(DungTheory) → la collection des extensions (Extension<DungTheory>) ;query(DungTheory, Argument) → Boolean, acceptation sceptique de l’argument.L’extension fondée est la plus prudente : on part de l’ensemble vide, on ajoute tout argument non attaqué, puis tout argument défendu, et on itère jusqu’à point fixe. Elle est toujours unique.
// Sur l'AF a -> b : l'extension fondée est {a} (a est inattaqué, b est battu par a)
AbstractExtensionReasoner grounded = new SimpleGroundedReasoner();
// getModels renvoie une java.util.Collection : on itère via toArray() (foreach C# cast chaque élément).
Console.WriteLine("Extensions fondées de AF :");
foreach (Extension ext in grounded.getModels(af).toArray(new Extension[0]))
Console.WriteLine(" " + ext);
// Acceptation sceptique : a accepté, b refusé
Console.WriteLine("\nAF, fondée ⊨ a ? " + grounded.query(af, a));
Console.WriteLine("AF, fondée ⊨ b ? " + grounded.query(af, b));
Extensions fondées de AF :
{a}
AF, fondée ⊨ a ? true
AF, fondée ⊨ b ? false
{a} ?Sur l’AF a -> b, l’extension fondée (grounded) est {a}. Cette sémantique se construit de façon sceptique et déterministe, par point fixe :
a n’est attaqué par personne, donc a entre dans l’extension.b, attaqué par a, est exclu.D’où le verdict de la cellule : grounded |= a vrai, grounded |= b faux. La force de la fondée est son unicité (toujours une seule extension, jamais d’ambiguïté) et sa complexité polynomiale. Sa faiblesse est sa prudence extrême : dès que deux arguments s’attaquent mutuellement, aucun des deux n’est accepté — c’est précisément ce que la cellule suivante met en évidence.
Sur l’AF à attaque mutuelle {x↔︎y} :
{} (vide — ni x ni y n’est défendu, le point fixe ne décolle pas) ;{x} et {y} (deux extensions — chaque argument bat l’autre) ;{x} et {y} (deux extensions maximales admissibles).C’est l’exemple canonique où sémantiques divergent : prudente (fondée) vs tranchantes (stable/préférée).
void Dump(string label, AbstractExtensionReasoner r, DungTheory theory)
{
Console.WriteLine(label + " :");
foreach (Extension ext in r.getModels(theory).toArray(new Extension[0]))
Console.WriteLine(" " + ext);
}
Dump("AF2 fondée", new SimpleGroundedReasoner(), af2);
Dump("AF2 stable", new SimpleStableReasoner(), af2);
Dump("AF2 préférée", new SimplePreferredReasoner(), af2);
Dump("AF2 complète", new SimpleCompleteReasoner(), af2);
AF2 fondée :
{}
AF2 stable :
{x}
{y}
AF2 préférée :
{x}
{y}
AF2 complète :
{x}
{y}
{}
Sur l’attaque mutuelle x <-> y, les quatre sémantiques divergent — c’est le moment où leur hiérarchie d’inclusions devient lisible :
| Sémantique | Extensions | Lecture |
|---|---|---|
| Fondée | {} |
Aucun argument n’est inattaqué, donc rien n’entre. Prudence maximale. |
| Stable | {x}, {y} |
Accepter l’un exclut l’autre. {} n’est pas stable : un stable doit attaquer tout non-membre, or {} n’attaque personne. |
| Préférée | {x}, {y} |
Maximaux parmi les admissibles. Ici, coïncide avec le stable. |
| Complète | {x}, {y}, {} |
Comme la préférée plus {} : l’extension vide est admissible, et elle est complète ici car aucun argument n’est inattaqué (sur d’autres AF, {} cesse d’être complète — l’Exercice 3 le montrera). |
La cascade d’inclusions canonique : fondée ⊆ complète, et la fondée est la plus petite extension complète (ici {}). stable ⊆ complète quand des stables existent ; préférée = complète maximale. C’est cette hiérarchie que les exercices ci-dessous vous demanderont d’explorer sur des AF plus subtils (cycle à trois, auto-attaque).
Construisons un AF où un argument est attaqué mais défendu par un autre :
d
|
v
c <- a -> b
a attaque b et c ;d attaque a (donc d défend indirectement b et c).L’extension fondée devrait contenir d (inattaqué), qui défend b et c contre a.
var A = new Argument("A");
var B = new Argument("B");
var C = new Argument("C");
var D = new Argument("D");
var af3 = new DungTheory();
af3.add(A); af3.add(B); af3.add(C); af3.add(D);
af3.add(new Attack(A, B)); // A attaque B
af3.add(new Attack(A, C)); // A attaque C
af3.add(new Attack(D, A)); // D attaque A (défend B et C)
Console.WriteLine("AF3 = " + af3);
// Extension fondée : D (inattaqué) défend B et C contre A -> {D, B, C}
AbstractExtensionReasoner gr = new SimpleGroundedReasoner();
foreach (Extension ext in gr.getModels(af3).toArray(new Extension[0]))
Console.WriteLine("AF3 fondée = " + ext);
// Acceptation sceptique
Console.WriteLine("\nAF3 fondée ⊨ D ? " + gr.query(af3, D));
Console.WriteLine("AF3 fondée ⊨ B ? " + gr.query(af3, B));
Console.WriteLine("AF3 fondée ⊨ A ? " + gr.query(af3, A)); // battu par D
AF3 = <{ A, B, C, D },[(A,B), (A,C), (D,A)]>
AF3 fondée = {B,C,D}
AF3 fondée ⊨ D ? true
AF3 fondée ⊨ B ? true
AF3 fondée ⊨ A ? false
Sur AF3 = {A->B, A->C, D->A}, l’extension fondée est {B, C, D}. C’est l’illustration du mécanisme de défense qui distingue l’argumentation de la simple élimination :
D est inattaqué → entre (germe du raisonnement).D attaque A → A est battu et exclu.A exclu, ses attaques sur B et C s’évanouissent → B et C redeviennent inattaqués et entrent à leur tour.D défend B et C en neutralisant leur attaquant commun A. C’est pourquoi grounded |= D et grounded |= B sont vrais, mais grounded |= A est faux. Un argument peut donc être accepté non parce qu’il est inattaqué, mais parce qu’il est défendu par un allié déjà accepté — ce mécanisme itératif est le cœur des systèmes d’argumentation abstraite.
Stubs sans
throw/raise(convention C.1) : le notebook s’exécute de bout en bout même non complété.
Construisez un AF en cycle p → q → r → p (chaque argument en attaque un autre, formant un 3-cycle). Affichez l’extension fondée et les extensions stables.
Question : combien d’extensions stables obtient-on ? L’extension fondée est-elle vide ? (Indice : dans un cycle impair sans argument inattaqué, le point fixe fondé ne décolle pas. Pour les stables, testez chaque candidat un à un : un singleton est stable s’il bat tous les non-membres — la situation est plus subtile que le 2-cycle x ↔︎ y de la section 3.2, où chaque singleton battait l’unique non-membre.)
// TODO etudiant : cycle p -> q -> r -> p
// Indice : declarez p,q,r, ajoutez 3 Attack, puis getModels(SimpleGroundedReasoner) et (SimpleStableReasoner).
object nbStables = null; // TODO etudiant : nombre d'extensions stables (int)
Console.WriteLine("extensions stables du cycle = " + (nbStables ?? "Exercice a completer"));
extensions stables du cycle = Exercice a completer
Fondée : ∅, sans surprise — aucun argument n’est inattaqué, le point fixe ne décolle pas.
Stables : zéro. C’est le résultat qui surprend, alors démontrons-le sur les candidats :
{p} est-il stable ? Il doit battre tous ses non-membres. p attaque q… mais pas r (c’est r qui attaque p). r est non-membre et non battu → {p} n’est pas stable. Symétriquement pour {q} et {r}.{p,q} porte l’attaque interne p → q → pas sans conflit. {q,r} et {r,p} pareil. Aucune paire n’est candidate.{p,q,r} n’est pas sans conflit non plus. Aucun ensemble sans conflit ne bat tout le reste : l’AF n’a aucune extension stable.L’intuition à retenir : le 2-cycle x ↔︎ y (section 3.2) offrait deux stables parce qu’un singleton y battait l’unique non-membre. Dans un 3-cycle, chaque argument n’en bat qu’un sur deux — le cycle impair est le cas d’école de la non-existence des stables (les cycles pairs, eux, offrent les deux stables alternés : sur p→q→r→s→p, {p,r} et {q,s}). Et c’est exactement pour ça que la théorie de Dung ne repose pas sur la sémantique stable : elle peut ne rien renvoyer du tout. La préférée, elle, existe toujours (au pire ∅), et la fondée existe toujours et est unique — deux garanties d’existence que la stable n’offre pas.
Un argument qui s’attaque lui-même (s → s) est dit auto-réfuté. Construisez un AF avec un seul argument s qui s’attaque, et vérifiez que aucune sémantique admissible ne l’accepte : l’extension fondée doit être vide, et query(af, s) doit valoir false.
Indice : af.add(new Attack(s, s)).
AF auto-attaque, fondée ⊨ s ? Exercice a completer
Le verdict attendu tombe : fondée = ∅, et query(af, s) = false. La raison tient en une définition : une extension doit être sans conflit, et un ensemble contenant s porte l’attaque s → s — le conflit est interne à l’argument lui-même. s ne peut donc figurer dans aucune extension, pour aucune sémantique (fondée, complète, préférée, stable : toutes exigent l’absence de conflit interne).
Deux observations bonus, à vérifier avec Dump :
∅, devrait battre s pour être stable — or ∅ n’attaque personne. L’AF à auto-attaque est un deuxième exemple (avec le 3-cycle de l’Exercice 1) d’inexistence des stables.{∅} : l’extension vide est trivialement admissible (aucun conflit, rien à défendre) et complète. C’est le plancher commun de toutes les sémantiques.En pratique, l’auto-attaque est la manière la plus économique de rendre un argument inacceptable : le moteur de raisonnement n’a même pas besoin d’examiner le reste du graphe. Dans les cadres structurés (Tweety-4, Aspic+), une prémisse contradictoire ou une règle auto-réfutée se ramène souvent à ce schéma abstrait.
Sur l’AF {a → b, a → c} (où a attaque b et c, mais rien n’attaque a), affichez les extensions complètes et préférées, puis répondez aux deux questions :
{} figurait parmi les complètes. Est-elle encore complète ici ? (Indice : une extension complète doit contenir tout argument qu’elle défend ; qui {} défend-elle quand a est inattaqué ?)Seconde question-piège : {a, b} est-elle admissible ? (b et c ne s’attaquent pas mutuellement — mais est-ce la bonne question à se poser ?)
// TODO etudiant : AF {a -> b, a -> c}, extensions completes vs preferees
// Indice : utilisez Dump("complete", new SimpleCompleteReasoner(), af) et ("preferee", new SimplePreferredReasoner(), af).
string verdict = null; // TODO etudiant : quelle extension est commune aux deux sémantiques ?
Console.WriteLine("extension commune complète/préférée = " + (verdict ?? "Exercice a completer"));
extension commune complète/préférée = Exercice a completer
{} n’est PAS complète iciLe décompte attendu :
| Sémantique | Extensions |
|---|---|
| Fondée | {a} |
| Complète | {a} seulement |
| Préférée | {a} |
| Stable | {a} |
(1) Complète et préférée sont confondues : {a} — l’extension commune. Sur cet AF, la complète n’admet aucune extension non-maximale en plus de la préférée.
(2) Le résultat le plus instructif : contrairement au 2-cycle de la section 3.2, {} n’est pas complète. Rappel de la définition : E est complète si E est admissible et contient tout argument qu’elle défend. Or « défendre » un argument sans attaquants est une condition vide — {} défend donc a (inattaqué), et une extension complète devrait alors contenir a. {} ne le contient pas : elle n’est pas complète. Retenez la règle exacte : {} est admissible sur tout AF, mais complète seulement si aucun argument n’est inattaqué — c’était le cas du 2-cycle (chaque argument y est attaqué), pas ici.
La question-piège : {a, b} n’est pas admissible — et la raison n’est pas l’absence d’attaque entre b et c, mais l’attaque a → b interne à l’ensemble : un ensemble sans conflit ne peut pas contenir un argument et sa cible. Quant à b et c, ils sont battus par a et personne ne les défend (défendre b exigerait d’attaquer a — rien ne l’attaque) : ils restent hors de toute extension non vide.
Les AF rencontrés dans ce notebook se rangent en une grille unique — la garder sous les yeux vaut toutes les définitions :
| AF | Fondée | Complète | Préférée | Stable | Ce que l’AF enseigne |
|---|---|---|---|---|---|
a → b |
{a} |
{a} |
{a} |
{a} |
cas dégénéré : tout coïncide |
x ↔︎ y (2-cycle) |
∅ |
{x},{y},∅ |
{x},{y} |
{x},{y} |
la fondée se trompe par prudence |
A→B, A→C, D→A (chaîne) |
{B,C,D} |
{B,C,D} |
{B,C,D} |
{B,C,D} |
la défense réhabilite les battus |
p→q→r→p (3-cycle) |
∅ |
∅ |
∅ |
aucune | cycle impair : la stable disparaît |
s → s (auto-attaque) |
∅ |
∅ |
∅ |
aucune | l’inacceptable universel |
Une phrase par sémantique : la fondée est le point fixe sceptique — unique, calculable en polynomial, mais parfois trop prudente (x ↔︎ y). La complète ajoute à l’admissibilité la règle « tout argument défendu est accepté » — c’est la plus fidèle à la notion de position rationnelle. La préférée en prend les maximaux — le choix standard quand on veut TOUTES les positions maximales défendables. La stable exige de battre activement chaque outsider — la plus agressive, et la seule sans garantie d’existence.
Le rapport d’inclusion qui structure tout : fondée ⊆ chaque complète ⊆ quelque préférée, et ∅ est admissible partout mais complète seulement sans argument inattaqué (comparez les lignes a → b et x ↔︎ y — c’est la leçon de l’Exercice 3), stable ⊆ préférée quand des stables existent (les deux dernières lignes du tableau montrent ce qui arrive sinon). Les trois garanties de Dung (1995) à retenir : la fondée existe et est unique ; la préférée existe toujours ; la stable n’est garantie nulle part.
Choisir en pratique : position unique et incontestable → fondée ; énumérer les lectures maximales d’un débat → préférée ; modéliser un système où tout non-accepté doit être « réfuté » (configurations, game theory) → stable, en vérifiant d’abord qu’elle existe. La suite naturelle dans la série : Tweety-4-Aspic-Csharp (argumentation structurée : les arguments deviennent des déductions, les attaques des rebuts/undercuts) et Tweety-7a-Extended-Frameworks-Csharp (attaques sur attaques, bipolarité).
On a porté en C#/.NET natif (sans JVM) l’argumentation abstraite de Dung — le cœur historique de TweetyProject — via le module arg-dung et IKVM :
DungTheory + Argument + Attack), y compris attaques mutuelles, cycles et défense indirecte.getModels.query.| Sémantique | Stratégie | Quand l’utiliser |
|---|---|---|
| Fondée | la plus prudente (point fixe) | décision unique, conservatrice |
| Complète | tout argument défendu est inclus | fermeture « naturelle » |
| Préférée | maximale pour l’inclusion | « le mieux possible » sans arbitrer |
| Stable | bat tout l’extérieur | verdicts nets, mais peut ne pas exister |