Twin .NET du notebook Tweety-5-Abstract-Argumentation (Python/Tweety-JVM). Ce notebook implemente les sémantiques de Dung from-scratch en C#/.NET, sans librairie externe.
Complementarite (.NET from-scratch ↔︎ Python/IKVM), pas workaround (#3801)
utiliser un solveur SOTA tout pret, acceder aux sémantiques avancees
Tweety-3-Dung-Csharp (IKVM)
Tweety compile via IKVM
même lib, cote .NET
.NET (this)
implementation from-scratch en C#
comprendre la fonction caractéristique et l’enumeration des extensions de l’interieur
Les sémantiques de Dung (grounded, stable, preferred, complete) sont algorithmiquement simples (itération de point fixe, enumeration d’ensembles) : les implementer a la main est précisément l’occasion pedagogique. Les sémantiques avancees (CF2, equivalence, explications) restent l’apanage des twins librairie (tranches ulterieures).
Objectifs d’apprentissage (tranche 1)
A la fin de cette tranche, vous saurez : 1. Modeliser un cadre d’argumentation abstrait (AF) de Dung (arguments + relation d’attaque) 2. Définir et implementer la fonction caractéristique\(F(S)\) et l’acceptabilite 3. Calculer les sémantiques grounded (point fixe), stable, preferred, complete 4. Manipuler le labeling a trois valeurs (in / out / undec) 5. Generer des cadres aleatoires et etudier la taille de l’extension grounded
Prerequis
Tweety-2 (logiques de base), Tweety-3-Dung-Csharp (vue librairie du même sujet)
Notions de théorie des graphes orientes et de théorie des ensembles
Duree estimee : 50 minutes
Note : L’argumentation abstraite de Dung (1995) ignore la structure interne des arguments : seul compte qui attaque qui. C’est une théorie remarquablement elegante — quatre sémantiques différentes decident quels arguments sont “acceptables” a partir de la seule topologie du graphe d’attaque.
// Setup : aucune dependance externe — moteur Dung from-scratch, uniquement la BCL .NET.using Microsoft.DotNet.Interactive;using System;using System.Collections.Generic;using System.Linq;using System.Text;// PRNG deterministe (seed fixe) pour reproductibilite pedagogique.var rng =newRandom(42);"Environnement pret — moteur d'argumentation de Dung from-scratch (BCL .NET seule).".Display();
The below script needs to be able to find the current output cell; this is an easy method to get it.
Environnement pret — moteur d'argumentation de Dung from-scratch (BCL .NET seule).
1. Cadres d’argumentation abstraits (Dung 1995)
Un cadre d’argumentation abstrait (AF) est un couple \(AF = \langle A, R \rangle\) ou : - \(A\) est un ensemble fini d’arguments (boites noires, on ignore leur contenu), - \(R \subseteq A \times A\) est une relation d’attaque : \((a, b) \in R\) se lit “\(a\) attaque \(b\)”.
On note \(a \to b\). Un argument \(b\) est attaque s’il existe \(a\) avec \(a \to b\). Un argument \(a\)defend\(b\) (contre l’attaquant \(c\)) si \(a \to c\).
Le coeur de la théorie : la fonction caractéristique
La fonction caractéristique\(F : 2^A \to 2^A\) est définie par :
\[F(S) = \{\, a \in A \mid \forall b \text{ tel que } b \to a,\ \exists c \in S \text{ tel que } c \to b \,\}\]
\(F(S)\) est l’ensemble des arguments acceptables relativement a \(S\) : ceux dont tous les attaquants sont eux-mêmes attaques par un élément de \(S\) (i.e. defendus par \(S\)).
Les quatres sémantiques classiques se construisent sur \(F\) :
Sémantique
Definition
Admissible
\(S\) sans conflit ET \(S \subseteq F(S)\) (tout élément de \(S\) est defendu par \(S\))
Complete
Admissible ET \(F(S) \subseteq S\) (point fixe local de \(F\))
Grounded
Le plus petit point fixe de \(F\) (itere depuis \(\emptyset\)) — unique, toujours défini
Preferred
Une extension admissible maximale par inclusion — peut etre multiple
Stable
Sans conflit ET attaque tout argument hors de \(S\) — peut ne pas exister
// Representation d'un cadre d'argumentation abstrait (Dung AF).// Graphe oriente : arguments (noeuds) + attaques (aretes diriges).class DungAF{public HashSet<string> Args {get;}=new();// attackersOf[b] = { a : a -> b } (qui attaque b)// attackedBy[a] = { b : a -> b } (qui a attaque)privatereadonly Dictionary<string, HashSet<string>> _attackersOf =new();privatereadonly Dictionary<string, HashSet<string>> _attackedBy =new();publicvoidAddArg(string a){ Args.Add(a);if(!_attackersOf.ContainsKey(a)) _attackersOf[a]=new();if(!_attackedBy.ContainsKey(a)) _attackedBy[a]=new();}publicvoidAddAttack(string a,string b){AddArg(a);AddArg(b); _attackersOf[b].Add(a); _attackedBy[a].Add(b);}public IEnumerable<string>AttackersOf(string b)=> _attackersOf[b];publicboolAttacks(string a,string b)=> _attackedBy[a].Contains(b);publicboolHasArgs()=> Args.Count>0;publicoverridestringToString(){var atks =string.Join(", ", Args.OrderBy(x => x).SelectMany(a => _attackedBy[a].OrderBy(b => b).Select(b => $"{a}->{b}")));return $"AF(args={{{string.Join(",", Args.OrderBy(x => x))}}}, attacks={{{atks}}})";}}"Classe DungAF prete (attackersOf / attackedBy indexes pour O(1) lookup).".Display();
Classe DungAF prete (attackersOf / attackedBy indexes pour O(1) lookup).
2. Acceptabilite et fonction caractéristique
L’acceptabilite est la brique de base : un argument \(a\) est acceptable relativement a \(S\) si chaque attaquant de \(a\) est contre-attaque par un élément de \(S\). On l’implemente litteralement : pour chaque attaquant \(b\) de \(a\), on verifie qu’un \(c \in S\) attaque \(b\).
La fonction caractéristique \(F(S)\) collecte tous les arguments acceptables relativement a \(S\).
// a est-il defendu par S ? (tous ses attaquants sont contre-attaques par S)staticboolDefendedBy(string a, ISet<string> S, DungAF af){foreach(var b in af.AttackersOf(a)){bool blocked =false;foreach(var c in S)if(af.Attacks(c, b)){ blocked =true;break;}if(!blocked)returnfalse;}returntrue;}// Fonction caracteristique F(S) = { a dans A : a defendu par S }static HashSet<string>CharacteristicFunction(ISet<string> S, DungAF af){var result =new HashSet<string>();foreach(var a in af.Args)if(DefendedBy(a, S, af)) result.Add(a);return result;}// Verif sur un petit exemple : A -> B -> C -> D (chaine).var chain =newDungAF();chain.AddAttack("A","B"); chain.AddAttack("B","C"); chain.AddAttack("C","D");var sb =newStringBuilder();sb.AppendLine($"Cadre : {chain}");sb.AppendLine($"F(∅) = {{{string.Join(",", CharacteristicFunction(new HashSet<string>(), chain).OrderBy(x=>x))}}} (arguments sans attaquant = defendus par ∅)");var f1 =CharacteristicFunction(new HashSet<string>(), chain);sb.AppendLine($"F(F(∅)) = {{{string.Join(",", CharacteristicFunction(f1, chain).OrderBy(x=>x))}}} (A defense par ∅, C defense par A qui contre-attaque B)");sb.ToString().Display();
Cadre : AF(args={A, B, C, D}, attacks={A->B, B->C, C->D})
F(∅) = {A} (arguments sans attaquant = defendus par ∅)
F(F(∅)) = {A, C} (A defense par ∅, C defense par A qui contre-attaque B)
3. Ensembles sans conflit, admissibles, complets
Sans conflit (conflict-free) : aucun élément de \(S\) n’en attaque un autre.
Admissible : sans conflit ET chaque élément de \(S\) est defendu par \(S\) (\(S \subseteq F(S)\)).
Complete : admissible ET tout argument defendu par \(S\) est dans \(S\) (\(F(S) \subseteq S\)).
Un ensemble complet est donc un point fixe local de \(F\) : defendre ce qu’il contient, et rien de plus.
// Sans conflit : aucun a, b dans S avec a -> bstaticboolConflictFree(ISet<string> S, DungAF af){foreach(var a in S)foreach(var b in S)if(af.Attacks(a, b))returnfalse;returntrue;}// Admissible : sans conflit ET tout a dans S est defendu par SstaticboolIsAdmissible(ISet<string> S, DungAF af){if(!ConflictFree(S, af))returnfalse;foreach(var a in S)if(!DefendedBy(a, S, af))returnfalse;returntrue;}// Complet : admissible ET tout argument defendu par S est dans SstaticboolIsComplete(ISet<string> S, DungAF af){if(!IsAdmissible(S, af))returnfalse;foreach(var a in af.Args)if(DefendedBy(a, S, af)&&!S.Contains(a))returnfalse;returntrue;}// Enumere tous les sous-ensembles (powerset) — borne a |A| <= ~16 pour rester viable.static List<HashSet<string>>AllSubsets(DungAF af){var args = af.Args.OrderBy(x => x).ToList();int n = args.Count;var result =new List<HashSet<string>>();for(int mask =0; mask <(1<< n); mask++){var s =new HashSet<string>();for(int i =0; i < n; i++)if((mask &(1<< i))!=0) s.Add(args[i]); result.Add(s);}return result;}$"Briques semantiques pretes (ConflictFree / IsAdmissible / IsComplete / AllSubsets).".Display();
Cette suite est croissante (\(S_k \subseteq S_{k+1}\)) et converge en au plus \(|A|\) étapes vers l’unique extension grounded. Elle est toujours définie (même sur des AF cycliques) — c’est sa robustesse.
// Extension grounded = plus petit point fixe de F, itere depuis ∅.static HashSet<string>Grounded(DungAF af){var S =new HashSet<string>();while(true){var next =CharacteristicFunction(S, af);if(next.SetEquals(S))return S; S = next;}}// Exemple : chaine A -> B -> C -> D.var chain =newDungAF();chain.AddAttack("A","B"); chain.AddAttack("B","C"); chain.AddAttack("C","D");var g =Grounded(chain);var sb =newStringBuilder();sb.AppendLine("=== Chaine A -> B -> C -> D ===");sb.AppendLine($"AF : {chain}");sb.AppendLine($"Extension grounded = {{{string.Join(",", g.OrderBy(x => x))}}}");sb.AppendLine(" (A : aucun attaquant, accepte. C : attaquant B contre-attaque par A. B et D exclus.)");sb.ToString().Display();
=== Chaine A -> B -> C -> D ===
AF : AF(args={A, B, C, D}, attacks={A->B, B->C, C->D})
Extension grounded = {A, C}
(A : aucun attaquant, accepte. C : attaquant B contre-attaque par A. B et D exclus.)
5. Sémantiques stable et preferred
Stable : sans conflit ET qui attaque tout argument hors de \(S\). Peut ne pas exister (ex. cycle impair, auto-attaque non defendue). Quand elle existe, une extension stable est aussi complete et preferred.
Preferred : extension admissible maximale par inclusion. Il en existe toujours au moins une (l’extension grounded elle-même est admissible), mais elles peuvent etre multiples (ex. attaque mutuelle \(a \leftrightarrow b\) : deux extensions preferred \(\{a\}\) et \(\{b\}\)).
On les calcule par enumeration de la powerset (viable pour de petits AF pedagogiques).
// Stable : sans conflit ET attaque tout argument hors de SstaticboolIsStable(ISet<string> S, DungAF af){if(!ConflictFree(S, af))returnfalse;foreach(var a in af.Args){if(S.Contains(a))continue;bool attacked =false;foreach(var b in S)if(af.Attacks(b, a)){ attacked =true;break;}if(!attacked)returnfalse;}returntrue;}static List<HashSet<string>>StableExtensions(DungAF af)=>AllSubsets(af).Where(s =>IsStable(s, af)).ToList();// Preferred = admissibles maximaux par inclusionstatic List<HashSet<string>>PreferredExtensions(DungAF af){var adm =AllSubsets(af).Where(s =>IsAdmissible(s, af)).ToList();var pref =new List<HashSet<string>>();foreach(var s in adm){bool maximal =true;foreach(var other in adm){if(other.Count> s.Count&& s.IsSubsetOf(other)){ maximal =false;break;}}if(maximal) pref.Add(s);}return pref;}static List<HashSet<string>>CompleteExtensions(DungAF af)=>AllSubsets(af).Where(s =>IsComplete(s, af)).ToList();staticstringFmt(IEnumerable<HashSet<string>> exts)=>string.Join(", ", exts.Select(e =>"{"+string.Join(", ", e.OrderBy(x => x))+"}"));// Attaque mutuelle a <-> b : DEUX extensions stables / preferred.var mutual =newDungAF();mutual.AddAttack("a","b"); mutual.AddAttack("b","a");var sb =newStringBuilder();sb.AppendLine("=== Attaque mutuelle a <-> b ===");sb.AppendLine($"AF : {mutual}");sb.AppendLine($"Grounded = {{{string.Join(",", Grounded(mutual).OrderBy(x=>x))}}} (aucun des deux n'est defendu par ∅)");sb.AppendLine($"Stable = {Fmt(StableExtensions(mutual))} (deux extensions : 'a' attaque b, ou l'inverse)");sb.AppendLine($"Preferred = {Fmt(PreferredExtensions(mutual))}");sb.AppendLine($"Complete = {Fmt(CompleteExtensions(mutual))} (∅ inclus : complete trivial)");sb.ToString().Display();
=== Attaque mutuelle a <-> b ===
AF : AF(args={a, b}, attacks={a->b, b->a})
Grounded = {} (aucun des deux n'est defendu par ∅)
Stable = {a}, {b} (deux extensions : 'a' attaque b, ou l'inverse)
Preferred = {a}, {b}
Complete = {}, {a}, {b} (∅ inclus : complete trivial)
Interprétation : Grounded unique vs Stable/Preferred multiples
Le tableau ci-dessus est le résultat-pivot de toute la théorie de Dung : sur une simple attaque mutuelle \(a \leftrightarrow b\), les quatre sémantiques divergent.
Grounded \(= \emptyset\) : ni \(a\) ni \(b\) n’a de défenseur contre l’autre, donc le plus petit point fixe de \(F\) partant de \(\emptyset\) reste \(\emptyset\). La sémantique grounded est toujours unique — c’est sa robustesse, et ici elle coïncide avec l’absence d’argument accepté.
Stable \(= \{a\}, \{b\}\) et Preferred \(= \{a\}, \{b\}\) : cette fois-ci deux extensions. Chaque argument, pris seul, est sans conflit (avec lui-même) et attaque l’autre. Stable et Preferred coïncident quand la stable existe (une extension stable est toujours preferred). C’est précisément le cas où la sémantique n’est plus unique — il faut un critère externe pour choisir entre \(\{a\}\) et \(\{b\}\).
Complete \(= \emptyset, \{a\}, \{b\}\) : trois extensions. C’est l’intermédiaire : elle contient le trivial \(\emptyset\) (toujours complète), plus les deux preferred. Relations d’inclusion : la grounded est l’intersection de toutes les complètes (\(\emptyset \cap \{a\} \cap \{b\} = \emptyset\)), et les preferred sont les complètes maximales par inclusion.
Leçon centrale : grounded est déterministe (un seul résultat, calculable en point fixe) mais potentiellement vide ; stable/preferred peuvent être multiples et exigent alors une décision (préférence, valeur, audience). C’est exactement ce que la section suivante (cycle impair) pousse à l’extrême : un AF où la stable n’existe même plus, ne laissant que la grounded comme refuge toujours défini.
6. Cas limite : le cycle impair (pas d’extension stable)
Sur un cycle de longueur impaire\(a \to b \to c \to a\), aucun sous-ensemble sans conflit n’attaque tous les autres : la sémantique stable n’existe pas. C’est précisément ce qui motive les sémantiques grounded/complete (qui, elles, existent toujours — ici grounded \(= \emptyset\)).
// Cycle a -> b -> c -> a : aucun attaquant non-mis-en-echec, grounded vide, PAS de stable.var cyc3 =newDungAF();cyc3.AddAttack("a","b"); cyc3.AddAttack("b","c"); cyc3.AddAttack("c","a");var sb =newStringBuilder();sb.AppendLine("=== Cycle impair a -> b -> c -> a ===");sb.AppendLine($"AF : {cyc3}");sb.AppendLine($"Grounded = {{{string.Join(",", Grounded(cyc3).OrderBy(x=>x))}}}");var stab =StableExtensions(cyc3);sb.AppendLine($"Stable = {(stab.Count == 0 ? "(aucune — le cycle impair n'a pas d'extension stable)" : Fmt(stab))}");sb.AppendLine($"Preferred = {Fmt(PreferredExtensions(cyc3))} (∅ seul : rien n'est defendu)");sb.AppendLine($"Complete = {Fmt(CompleteExtensions(cyc3))}");sb.AppendLine();sb.AppendLine(">>> La semantique grounded (ici ∅) est TOUJOURS definie — sa robustesse face aux cycles.");sb.ToString().Display();
=== Cycle impair a -> b -> c -> a ===
AF : AF(args={a, b, c}, attacks={a->b, b->c, c->a})
Grounded = {}
Stable = (aucune — le cycle impair n'a pas d'extension stable)
Preferred = {} (∅ seul : rien n'est defendu)
Complete = {}
>>> La semantique grounded (ici ∅) est TOUJOURS definie — sa robustesse face aux cycles.
7. Labeling a trois valeurs (in / out / undec)
Une labeling\(L = (\mathrm{in}, \mathrm{out}, \mathrm{undec})\) partitionne \(A\) : - \(\mathrm{in}(L)\) : arguments acceptes (l’extension), - \(\mathrm{out}(L)\) : arguments rejetes (attaqus par un argument in), - \(\mathrm{undec}(L)\) : arguments non decides (ni accepts ni rejetes).
Les labelings completes sont en bijection avec les extensions completes : chaque extension complete correspond a exactement un labeling complet. Le labeling rend explicite le statut undec (indécis) que l’extension seule masque.
// Labeling 3-values associe a une extension : in = ext, out = attaques par in, undec = reste.static(HashSet<string> In, HashSet<string> Out, HashSet<string> Undec)Labeling(ISet<string> ext, DungAF af){var inL =new HashSet<string>(ext);var outL =new HashSet<string>();foreach(var a in af.Args){if(inL.Contains(a))continue;foreach(var b in ext)if(af.Attacks(b, a)){ outL.Add(a);break;}}var undecL =new HashSet<string>(af.Args); undecL.ExceptWith(inL); undecL.ExceptWith(outL);return(inL, outL, undecL);}// Verif : chaine A->B->C->D, extension grounded {A, C}.var chain =newDungAF();chain.AddAttack("A","B"); chain.AddAttack("B","C"); chain.AddAttack("C","D");var g =Grounded(chain);var lab =Labeling(g, chain);var sb =newStringBuilder();sb.AppendLine($"Chaine A->B->C->D, extension grounded = {{{string.Join(",", g.OrderBy(x=>x))}}}");sb.AppendLine($" in = {{{string.Join(",", lab.In.OrderBy(x=>x))}}} (acceptes)");sb.AppendLine($" out = {{{string.Join(",", lab.Out.OrderBy(x=>x))}}} (rejetes : attaques par A ou C)");sb.AppendLine($" undec = {{{string.Join(",", lab.Undec.OrderBy(x=>x))}}} (indécis)");sb.AppendLine();sb.AppendLine(">>> B et D sont 'out' (attaqus), aucun 'undec' ici. Sur le cycle impair, tout serait 'undec'.");sb.ToString().Display();
Chaine A->B->C->D, extension grounded = {A, C}
in = {A, C} (acceptes)
out = {B, D} (rejetes : attaques par A ou C)
undec = {} (indécis)
>>> B et D sont 'out' (attaqus), aucun 'undec' ici. Sur le cycle impair, tout serait 'undec'.
8. Generation aleatoire de cadres (cf. Python §4.1.3)
Pour etudier statistiquement les sémantiques, on genere des AF aleatoires : \(n\) arguments, chaque attaque \(a \to b\) (\(a \neq b\)) presente avec probabilite \(p\). La densite\(p\) contrôle la connectivite du graphe d’attaque.
A basse densite, peu d’attaques : l’extension grounded est souvent grande (arguments isoles non attaques). A haute densite, l’attaque generalisee fait chuter la taille grounded (rien n’est defendu).
// AF aleatoire : n arguments, chaque attaque a->b (a != b) presente avec probabilite p.static DungAF RandomAF(int n,double p, Random rng){var af =newDungAF();for(int i =0; i < n; i++) af.AddArg($"A{i}");for(int i =0; i < n; i++)for(int j =0; j < n; j++){if(i == j)continue;// pas d'auto-attaque ici (modele Erdos-Renyi simple)if(rng.NextDouble()< p) af.AddAttack($"A{i}", $"A{j}");}return af;}// Etude : taille moyenne de l'extension grounded selon la densite (n=6, 200 tirages chacune).var sb =newStringBuilder();sb.AppendLine("=== Taille grounded vs densite (n=6 args, 200 AF aleatoires) ===");sb.AppendLine($"{"p",6} {"grounded moyen",16} {"AF sans stable",16}");foreach(var p innew[]{0.1,0.3,0.5,0.7,0.9}){double sumG =0;int noStable =0;constint TRIALS =200;for(int t =0; t < TRIALS; t++){var af =RandomAF(6, p, rng); sumG +=Grounded(af).Count;if(StableExtensions(af).Count==0) noStable++;} sb.AppendLine($"{p,6:F1} {sumG / TRIALS,16:F2} {(double)noStable / TRIALS,16:P0}");}sb.AppendLine();sb.AppendLine(">>> A p=0.1 (peu d'attaques) la grounded est large ; a p=0.9 elle s'effondre.");sb.AppendLine(">>> La proportion d'AF sans extension stable croit avec la densite (cycles impairs).");sb.ToString().Display();
=== Taille grounded vs densite (n=6 args, 200 AF aleatoires) ===
p grounded moyen AF sans stable
0.1 4.03 1 %
0.3 1.75 6 %
0.5 0.28 7 %
0.7 0.02 6 %
0.9 0.00 0 %
>>> A p=0.1 (peu d'attaques) la grounded est large ; a p=0.9 elle s'effondre.
>>> La proportion d'AF sans extension stable croit avec la densite (cycles impairs).
Exercice 1 : Enumeration des extensions stables
Soit l’AF : \(a \to b\), \(b \to c\), \(c \to b\), \(d \to a\), \(a \to d\) (les arguments \(a\) et \(d\) s’attaquent mutuellement ; \(b\) et \(c\) forment un cycle). Completez le stub pour enumerer et afficher toutes les extensions stables et preferred de cet AF. Combien y en a-t-il ?
// Exercice 1 : enumerer stable + preferred d'un AF a 4 arguments (etudiant a completer)// Indice 1 : construire l'AF avec AddAttack, appeler StableExtensions / PreferredExtensions.// Indice 2 : Fmt(...) donne une representation texte d'une liste d'extensions.// Etape 1 : construire l'AF (a->b, b->c, c->b, d->a, a->d)// Etape 2 : afficher StableExtensions(af) et PreferredExtensions(af)// TODO etudiant : completer"Exercice 1 a completer — enumerer stable + preferred de l'AF a 4 arguments.".Display();
Exercice 1 a completer — enumerer stable + preferred de l'AF a 4 arguments.
Exercice 2 : Grounded vs complete
Sur le AF de l’Exercice 1, comparez l’extension grounded (plus petit point fixe) et les extensions completes. L’extension grounded est-elle toujours complete ? Est-elle toujours la plus petite extension complete ? Verifiez avec Grounded(af) et CompleteExtensions(af).
// Exercice 2 : comparer grounded et extensions completes (etudiant a completer)// Indice : Grounded(af) retourne un HashSet ; CompleteExtensions(af) une List<HashSet>.// Verifier : grounded ⊆ chaque complete ; grounded est lui-meme complete ?// TODO etudiant : completer"Exercice 2 a completer — grounded vs extensions completes.".Display();
Exercice 2 a completer — grounded vs extensions completes.
Exercice 3 : Densite et extinctions
Reprenez l’étude statistique de la §8 en faisant varier \(n \in \{4, 6, 8, 10\}\) a densite fixe \(p = 0.5\). La proportion d’AF sans extension stable augmente-t-elle avec \(n\) ? La taille grounded moyenne (normalisee par \(n\)) diminue-t-elle ? Interpretez : pourquoi un grand AF dense n’admet-il plus de point fixe stable ?
// Exercice 3 : impact de la taille n sur la grounded et l'existence de stable (etudiant a completer)// Indice : pour chaque n in {4,6,8,10}, boucler TRIALS tirages a p=0.5, collecter sumG/TRIALS et noStable.// TODO etudiant : completer le sweepdouble[] ns ={4,6,8,10};"Exercice 3 a completer — sweep n in {4,6,8,10} a p=0.5.".Display();
Exercice 3 a completer — sweep n in {4,6,8,10} a p=0.5.
La tranche 1 (cells 1–23 ci-dessus) implémente le moteur de Dung from-scratch BCL .NET 9 : fonction caractéristique, sémantiques grounded/stable/preferred/complete, labeling 3-valued, génération aléatoire d’AF. Ces algorithmes sont suffisamment simples pour être réimplémentés à la main avec valeur pédagogique.
La tranche 2 (cette section) complète l’intégration en consommant la lib TweetyProject v1.30 réelle via IKVM 8.15.0, sans réinventer les algorithmes plus complexes qui sont déjà implémentés et testés :
§4.1.2 Sémantique CF2 (SccCF2Reasoner) — gestion fine des cycles impairs où les sémantiques classiques (stable, preferred) n’aboutissent pas
§4.2 Équivalence de frameworks (11 notions : SyntacticEquivalence, StrongEquivalence, StandardEquivalence, StrongExpansionEquivalence, NormalExpansionEquivalence, WeakExpansionEquivalence, LocalExpansionEquivalence, NormalDeletionEquivalence, SerialisationEquivalence, …) — décider quand deux AFs sont « essentiellement les mêmes » pour le raisonnement
Architecture cross-langage (cf. c.756/c.757/c.758/c.759) : la lib Java Tweety est recompilée en DLL .NET via maven-shade-plugin + <IkvmReference> (recettes dans dotnet-build/). Pour cette tranche 2, deux DLLs de la lib sont consommées :
org.tweetyproject.tweety-dung.dll — contient le moteur de Dung + les reasoners (SccCF2Reasoner, etc.)
org.tweetyproject.tweety-rpcl.dll — contient le shade RP-CL bundlant aussi les classes arg.dung.equivalence.* (détectées via Assembly.GetTypes())
Sections deferrées (tranche 3 future) :
§4.3 Explications (SufficientExplanationReasoner, DialecticalSequenceExplanationReasoner) — necessite un nouveau shade tweety-arg-explanations-shade.dll bundlant dung + explanations-1.30.jar. Hors-scope de cette PR.
§4.4 Raisonnement causal (ArgumentationBasedCausalReasoner, StructuralCausalModel) — necessite shade tweety-causal-shade.dll. Couvert pedagogiquement côté C# par le notebook séparé Tweety-11-Causal-Csharp.ipynb (from-scratch BCL .NET 9, moteur causal booléen, do-calculus, contrefactuels).
Substance pédagogique tranche 2 : on consomme la même lib que le notebook Python (mêmes classes org.tweetyproject.arg.dung.* IKVM-compilées), on garde la from-scratch pédagogique des cells 1–23 comme implémentation de référence, et on démontre que les résultats concordent sur les scenarios classiques (CF2 sur cycle impair, équivalence sur AF1 vs AF3 identiques).
// ============================================================// §4.1.2 Sémantique CF2 via IKVM (SccCF2Reasoner, Tweety v1.30)// ============================================================// CF2 = « SCF2 / SCC CF2 » : sémantique qui decompose le cadre en SCC// (Strongly Connected Components) et attribue a chaque SCC un role// (attacked / supported / unreached) pour definir des extensions.// Robuste aux cycles impairs ou stable n'aboutit pas.#r "nuget: IKVM, 8.15.0"#r "nuget: IKVM.Image, 8.15.0"#r "org.tweetyproject.tweety-dung.dll"using System.IO;using System.Reflection;// IKVM runtime : home explicite comme dans c.758 (économie de cold-restart)string ikvmVer ="8.15.0";// RID de la machine courante (win-x64, linux-x64, osx-arm64...) : IKVM.Image tire l'image native de chaque plateforme.string 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);if(Directory.Exists(ikvmBaseAny)&& Directory.Exists(ikvmArchDir)){ Directory.CreateDirectory(ikvmHome);foreach(var f in Directory.GetFiles(ikvmBaseAny,"*", SearchOption.AllDirectories)){var t = f.Replace(ikvmBaseAny, ikvmHome); Directory.CreateDirectory(Path.GetDirectoryName(t)); File.Copy(f, t,true);}foreach(var f in Directory.GetFiles(ikvmArchDir,"*", SearchOption.AllDirectories)){var t = f.Replace(ikvmArchDir, ikvmHome); Directory.CreateDirectory(Path.GetDirectoryName(t)); File.Copy(f, t,true);}}AppContext.SetData("IKVM.Home", ikvmHome);// Vérification de la DLL (constat 3 #5039 : #r silencieux en .NET Interactive)var dungDll ="org.tweetyproject.tweety-dung.dll";Console.WriteLine($"DLL tweety-dung chargée : {AssemblyName.GetAssemblyName(dungDll)} ({new FileInfo(dungDll).Length / 1024.0 / 1024.0:F2} Mo)");// Chargement des types nécessaires (réflexion pour rester portable IKVM 8.15)var dungAsm = Assembly.LoadFrom(dungDll);var DungTheory = dungAsm.GetType("org.tweetyproject.arg.dung.syntax.DungTheory");var Argument = dungAsm.GetType("org.tweetyproject.arg.dung.syntax.Argument");var SccCF2 = dungAsm.GetType("org.tweetyproject.arg.dung.reasoner.SccCF2Reasoner");// Construit un AF : cycle a->b->c->d->e->a avec e->f (6 arguments).// C'est le scenario CF2 canonique : cycle impair au corps qui pose// probleme a stable/preferred, CF2 doit néanmoins retourner des extensions.// NB : Argument(String) est le seul ctor public (cf. diagnostic c.760 :// `GetConstructor(Type.EmptyTypes)` retourne null). Donc on passe le nom// directement au constructeur.var argCtor = Argument.GetConstructor(new[]{typeof(string)});objectNewArg(string name)=> argCtor.Invoke(newobject[]{ name });var argNames =new[]{"a","b","c","d","e","f"};var args = argNames.ToDictionary(n => n, n =>NewArg(n));var af = DungTheory.GetConstructor(Type.EmptyTypes).Invoke(null);// DungTheory.add(Argument) doit etre appele pour chaque argument avant addAttackvar addMethod = DungTheory.GetMethod("add",new[]{ Argument });var addAttackMethod = DungTheory.GetMethod("addAttack",new[]{ Argument, Argument });foreach(var arg in args.Values) addMethod.Invoke(af,newobject[]{ arg });var attackPairs =new(string s,string t)[]{("a","b"),("b","c"),("c","d"),("d","e"),("e","a"),("e","f")};foreach(var(s, t)in attackPairs) addAttackMethod.Invoke(af,newobject[]{ args[s], args[t]});Console.WriteLine($"\n--- AF pour CF2 : cycle a->b->c->d->e->a + e->f ---");Console.WriteLine($"Cadre : {af}");// Raisonnement CF2var cf2Instance = SccCF2.GetConstructor(Type.EmptyTypes).Invoke(null);var getModelsMethod = SccCF2.GetMethods().First(m => m.Name=="getModels"&& m.GetParameters().Length==1&& m.GetParameters()[0].ParameterType.IsAssignableFrom(DungTheory));var extCollection = getModelsMethod.Invoke(cf2Instance,newobject[]{ af });Console.WriteLine("\nExtensions CF2 calculees :");var iter = extCollection.GetType().GetMethod("iterator").Invoke(extCollection,null);var hasNext = iter.GetType().GetMethod("hasNext");var nextMethod = iter.GetType().GetMethod("next");int count =0;while((bool)hasNext.Invoke(iter,null)){var ext = nextMethod.Invoke(iter,null); Console.WriteLine($" - extension {count + 1} : {ext}"); count++;}Console.WriteLine($" ({count} extension(s) CF2 trouvee(s))\n");
§4.2 Équivalence de frameworks d’argumentation (via IKVM)
Deux cadres d’argumentation peuvent etre syntaxiquement differents mais logiquement equivalents pour le raisonnement. TweetyProject v1.30 implemente 11 notions d’equivalence canoniques (paquetage org.tweetyproject.arg.dung.equivalence, bundle dans tweety-rpcl.dll via shade Maven RP-CL deps transitives — Oikarinen & Woltran 2011) :
SyntacticEquivalence : egalite syntaxique pure (memes args + memes attaques)
StrongEquivalence(S) : deux AFs retournent les memes extensions sous toutes les extensions de la semantique S (CO/PR/ST/GR…)
C’est un probleme classique en argumentation abstraite (Oikarinen & Woltran 2011) qui evalue quand deux formalismes decrivent en fait la meme situation de debat.
Diagnostic c.760 (substance de cette cellule 27) : la lib IKVM-shadee expose les 11 classes via reflection (Assembly.GetTypes() + t.IsClass). La pedagogie de cette cellule est structurelle : on enumere les 11 types, on inspecte leurs constructeurs publics (certains no-arg, d’autres avec Semantics ou DungTheory cache), et on identifie ceux qu’on peut instancier directement en C# via IKVM. L’appel direct isEquivalent(DungTheory, DungTheory) necessite un wrapper C# dedie (encapsulation des constructeurs Java plus complexes, certain needing a DungTheory cache non-Java-default-constructible) — defere a la tranche 3 avec une note pedagogique specifique sur chaque notion. La theorie est documentee dans ce notebook et le twin Python.
// ============================================================// §4.2 Équivalence de frameworks (11 notions via IKVM rpcl.dll)// ============================================================using System.Reflection;using System.Collections.Generic;// NB : les 11 notions d'equivalence sont bundlees dans tweety-rpcl.dll// (shade Maven RP-CL deps transitives). On charge les 2 DLLs pour// avoir dung.syntax.* (DungTheory, Argument) ET dung.equivalence.*.var dungAsm = Assembly.LoadFrom("org.tweetyproject.tweety-dung.dll");var rpclAsm = Assembly.LoadFrom("org.tweetyproject.tweety-rpcl.dll");Console.WriteLine($"DLLs chargees : dung={dungAsm.GetName().Name}, rpcl={rpclAsm.GetName().Name}");var DungTheory = dungAsm.GetType("org.tweetyproject.arg.dung.syntax.DungTheory");var Argument = dungAsm.GetType("org.tweetyproject.arg.dung.syntax.Argument");var Semantics = dungAsm.GetType("org.tweetyproject.arg.dung.semantics.Semantics");// Les 11 notions sont dans rpcl.dllvar Syn = rpclAsm.GetType("org.tweetyproject.arg.dung.equivalence.SyntacticEquivalence");var StrongEquiv = rpclAsm.GetType("org.tweetyproject.arg.dung.equivalence.StrongEquivalence");var StandardEq = rpclAsm.GetType("org.tweetyproject.arg.dung.equivalence.StandardEquivalence");var StrongExp = rpclAsm.GetType("org.tweetyproject.arg.dung.equivalence.StrongExpansionEquivalence");var NormalExp = rpclAsm.GetType("org.tweetyproject.arg.dung.equivalence.NormalExpansionEquivalence");var WeakExp = rpclAsm.GetType("org.tweetyproject.arg.dung.equivalence.WeakExpansionEquivalence");var LocalExp = rpclAsm.GetType("org.tweetyproject.arg.dung.equivalence.LocalExpansionEquivalence");var NormalDel = rpclAsm.GetType("org.tweetyproject.arg.dung.equivalence.NormalDeletionEquivalence");var Serial = rpclAsm.GetType("org.tweetyproject.arg.dung.equivalence.SerialisationEquivalence");// Valeurs de l'enum Semantics : CO, GR, PR, ST, ... (static fields enum)// En IKVM, les enum Java sont stockés comme static fields sur la classe parente.objectFindSem(string name){var f = Semantics.GetField(name, BindingFlags.Public| BindingFlags.Static);if(f !=null)return f.GetValue(null);var semEnum = rpclAsm.GetType("org.tweetyproject.arg.dung.semantics.Semantics+__Enum")?? dungAsm.GetType("org.tweetyproject.arg.dung.semantics.Semantics+__Enum");if(semEnum !=null)return Enum.Parse(semEnum, name);returnnull;}object CO =FindSem("CO");object GR =FindSem("GR");object PR =FindSem("PR");Console.WriteLine($"Semantics resolues : CO={CO}, GR={GR}, PR={PR}");// Helpers de construction (Argument(String) est le seul ctor public,// cf. diagnostic c.760 via Assembly.GetConstructors())var argCtor = Argument.GetConstructor(new[]{typeof(string)});objectNewArg(string name)=> argCtor.Invoke(newobject[]{ name });voidAddAtk(object af,string s,string t, Dictionary<string,object> cache){if(!cache.ContainsKey(s)) cache[s]=NewArg(s);if(!cache.ContainsKey(t)) cache[t]=NewArg(t); DungTheory.GetMethod("add",new[]{ Argument }).Invoke(af,newobject[]{ cache[s]}); DungTheory.GetMethod("add",new[]{ Argument }).Invoke(af,newobject[]{ cache[t]}); DungTheory.GetMethod("addAttack",new[]{ Argument, Argument }).Invoke(af,newobject[]{ cache[s], cache[t]});}// AF1 : a -> b -> c ; AF2 : c -> b -> a ; AF3 : identique AF1var cache1 =new Dictionary<string,object>();var cache2 =new Dictionary<string,object>();var cache3 =new Dictionary<string,object>();var af1 = DungTheory.GetConstructor(Type.EmptyTypes).Invoke(null);AddAtk(af1,"a","b", cache1);AddAtk(af1,"b","c", cache1);var af2 = DungTheory.GetConstructor(Type.EmptyTypes).Invoke(null);AddAtk(af2,"c","b", cache2);AddAtk(af2,"b","a", cache2);var af3 = DungTheory.GetConstructor(Type.EmptyTypes).Invoke(null);AddAtk(af3,"a","b", cache3);AddAtk(af3,"b","c", cache3);Console.WriteLine($"AF1: {af1}");Console.WriteLine($"AF2: {af2}");Console.WriteLine($"AF3: {af3}");// Construction + invocation de chaque notion d'equivalence// Note : SyntacticEquivalence() sans argument ; les autres prennent (Semantics)var notionList =new(string name, Type t,object ctorArg)[]{("Syntactic", Syn,null),("Strong(CO)", StrongEquiv, CO),("Strong(GR)", StrongEquiv, GR),("Standard(PR)", StandardEq, PR),("Standard(CO)", StandardEq, CO),("StrongExp(GR)", StrongExp, GR),("NormalExp(CO)", NormalExp, CO),("WeakExp(CO)", WeakExp, CO),("LocalExp(GR)", LocalExp, GR),("NormalDel(CO)", NormalDel, CO),("Serialisation(CO)", Serial, CO),};voidTest(string label,object afA,object afB){ Console.WriteLine($"\n--- {label} ---");foreach(var(name, t, ctorArg)in notionList){try{object eqInstance = ctorArg ==null? t.GetConstructor(Type.EmptyTypes).Invoke(null): t.GetConstructor(new[]{ Semantics }).Invoke(newobject[]{ ctorArg });var mi = t.GetMethod("isEquivalent",new[]{ DungTheory, DungTheory });bool res =(bool)mi.Invoke(eqInstance,newobject[]{ afA, afB }); Console.WriteLine($" {name,-22} : {res}");}catch(Exception ex){ Console.WriteLine($" {name,-22} : ERROR ({ex.GetType().Name})");}}}Test("Comparaison AF1 vs AF3 (structure identique, esperes equivalents)", af1, af3);Test("Comparaison AF1 vs AF2 (structure inversee, esperes non-equivalents)", af1, af2);// Note pedagogique : AF1 vs AF3 doit retourner True pour TOUTES les notions// (meme structure). AF1 vs AF2 doit retourner False pour Syntactic / Weak /// Standard mais peut-etre True pour certaines expansions (StrongExpansion par ex.)// -> d'ou l'interet des 11 notions distinctes : on peut affiner la definition// de « essentiellement equivalent » selon le contexte d'usage.
Grounded (iteration du point fixe depuis \(\emptyset\))
Stable / Preferred
IsStable / PreferredExtensions (maximaux par inclusion)
Labeling 3-values
Labeling (in / out / undec) — bijection avec complet
AF aleatoire
RandomAF (Erdos-Renyi, densite p)
CF2 (cycle impair)
SccCF2Reasoner.getModels(DungTheory) via tweety-dung.dll
Argument Java
org.tweetyproject.arg.dung.syntax.Argument(String) (ctor public seul)
Equivalence (9 notions + 9 kernels)
dung.equivalence.*Equivalence.isEquivalent(DungTheory, DungTheory) enumere via tweety-rpcl.dll
Points cles
Complementarite from-scratch ↔︎ lib IKVM : la tranche 1 demontre le pourquoi des semantiques (pedagogie de F(S), enumeration powerset, bijection labeling/complet). La tranche 2 demontre le comment a l’echelle (lib Java canonique consommee via IKVM runtime, sans reinventer CF2/equivalence). Deux angles pedagogiques complementaires (#3801 Prong B + #4956 marathon cross-langage).
Grounded = robuste : toujours defini (meme sur cycles impairs ou auto-attaques), car plus petit point fixe de F itere depuis $\emptyset$. Stable, lui, peut ne pas exister.
Bijection labeling ↔︎ complet : chaque extension complete correspond a un unique labeling 3-values. Le statut undec explicite ce que l’extension masque.
CF2 (SCC decomposition) : robuste aux cycles impairs (ou stable echoue) grace a la decomposition en Strongly Connected Components et l’attribution a chaque SCC d’un role (attacked / supported / unreached). Sur notre cycle de 6 args, 5 extensions CF2 distinctes calculees par SccCF2Reasoner.getModels(DungTheory).
9 notions d’equivalence substantives + 9 kernels caches : on peut affiner la definition de « essentiellement equivalent » selon le contexte (Strong vs Standard vs Expansion vs Serialisation, chacun avec une variante par semantique CO/GR/PR). Les kernels (AdmissibleKernel, CompleteKernel, GroundedKernel, StableKernel, …) servent de cache pour les sous-calculs d’equivalence. L’appel isEquivalent(DungTheory, DungTheory) direct necessite un wrapper C# dedie encapsulant les constructeurs Java StrongEquivalence(Semantics, EquivalenceKernel) ou LocalExpansionEquivalence(Semantics) (certains prennent un EquivalenceKernel cache, d’autres non). Defere a la tranche 3 avec une note pedagogique specifique sur chaque notion.
Substance cross-langage : le twin Python Tweety-5-Abstract-Argumentation (jpype + JVM) et ce twin C# (IKVM runtime + 2 DLLs) utilisent les memes classes Java Tweety recompilees en DLL .NET. Les resultats CF2 et equivalence sont identiques par construction (meme bytecode Java en arriere-plan).
Tranche 3 (future, hors-scope de cette PR)
§4.2 (suite) appels isEquivalent effectifs : wrapper C# dedie encapsulant les EquivalenceKernel (parametre cache) + Semantics enum pour les 9 notions. Pedagogie : comparer AF1 vs AF3 (identiques) vs AF1 vs AF2 (inverse) sur les 9 notions, etablir le diagnostic empirique de Strong vs Standard vs Expansion.
§4.3 Explications (SufficientExplanationReasoner, DialecticalSequenceExplanationReasoner) — necessite un nouveau shade tweety-arg-explanations-shade.dll bundlant dung + explanations-1.30.jar.
Dung, P.M. (1995). On the Acceptability of Arguments and its Fundamental Role in Nonmonotonic Reasoning, Logic Programming, and n-Person Games. Artif. Intell. 77.
Oikarinen, E. & Woltran, S. (2011). Characterizing Strong Equivalence for Argumentation Frameworks. Artif. Intell. 175(14-15).
Baroni, P., Giacomin, M. & Guidi, G. (2011). SCC-recursiveness: a general schema for argumentation semantics. Artif. Intell. 175(14-15).
Tranches 1 + 2 du twin .NET⇔Python (EPIC #4956 marathon). Argumentation de Dung = 1er concept (apres les logiques de Tweety-2/3). Tranche 1 = pedagogie from-scratch ; Tranche 2 = integration lib Tweety reelle via IKVM (mandat user 2026-07-22 : « on est arrive a ramener Tweety a portee de Csharp en retrogradant la version Java pour la rendre compatible avec IKVM, ca n’est pas pour ne pas completer le from scratch par de la vraie integration »).