⚠️ Re-fondation 2026-07-10 (Argumentum PR #763) — le générateur OWL des Fallacies a change d’idiome. Les concepts ne sont plus des NamedIndividual mais des owl:Class, et toutes les relations (hiérarchie SKOS, crossLink, AIF attack) sont serialisees en AnnotationAssertion (SKOSHelper d’OWLSharp etant casse sur cette version). Ce notebook a ete re-fonde en consequence : le parseur regex tolerant reste la bonne approche (rdflib et owlready2 echouent toujours), mais les structures extraites changent.
Ce notebook explore ontologies/argumentum_fallacies.owl (OWL2/XML, ~6,0 MB) et en extrait la structure sans passer par un parseur RDF strict.
Objectifs d’apprentissage :
Charger l’OWL2/XML via un parseur regex tolerant et comprendre pourquoi rdflib/owlready2 echouent sur ce dialecte.
Extraire les labels multilingues (skos:prefLabel, 2 816 = 1 408 FR + 1 408 EN) et reperer les schemes de Walton (100 classes de forme *_Inference_* materialisees depuis les mappings AIF_skos).
Inventorier les relations (AnnotationAssertion-resource) : hiérarchie (broader/narrower/inScheme), crossLink (leverages/isRelatedTo/inverts/… = 735) et AIF attack (aifAttackedNode = 145 → RA/I/CA-node).
Construire le sous-graphe de l’Equivoque (semanticAmbiguity) et montrer qu’il est desormais type par AIF (aifAttackedNode → RA-node).
Quatre exercices formels (règle #2161).
Sommaire :
Contexte : Argumentum, OWL2 et l’idiome annotation-space (#763)
Structure : Class + ObjectProperty + AnnotationAssertion
Schemes de Walton (100 classes materialisees + labels)
Relations : inventaire des AnnotationAssertion-resource (crossLink + AIF)
Grappe Equivoque : sous-graphe type AIF
Distribution des attaques AIF par type de node
Scheme vs conflict : l’attaque AIF est-elle un scheme ou un conflit ?
Exercices
Ponts avec la serie
1. Contexte : Argumentum, OWL2 et l’idiome annotation-space (#763)
L’ontologie Argumentum (https://www.argumentum.games) encode une taxonomie des sophismes (fallacies) et des vertus argumentatives a destination du jeu serieux Argumentum. Le fichier argumentum_fallacies.owl est genere par le pipeline .NET (OwlGeneratorConfig.cs) a partir du CSV canonique.
Idiome du générateur (après PR #763, mergee 2026-07-10) :
Chaque sophisme est une owl:Class (<Declaration><Class IRI="…#semanticAmbiguity"/></Declaration>) — et non plus un NamedIndividual comme dans l’ancien générateur. Le modèle AIF (RA-node/I-node/CA-node/Conflict) apporte 4 classes supplementaires (namespace arg.dundee.ac.uk/aif).
Toutes les relations passent par AnnotationAssertion : SKOSHelper d’OWLSharp est casse sur cette version, donc le générateur emet la hiérarchie SKOS (broader/narrower/inScheme), les 8 verbes crossLink et les 2 colonnes AIF attack comme des annotations (resource pour un lien concept→concept, literal pour une valeur textuelle).
Les IRI sont serialises en forme complete (IRI="…"), plus en abbreviatedIRI="skos:prefLabel" — d’ou la reecriture des regex de ce notebook.
Pourquoi un parseur regex ? rdflib echoue au parse (dialecte OWL2/XML + annotations resource non standard) et owlready2 charge le fichier mais n’expose 0 classe via son API Python. Le parseur regex tolerant reste le chemin fiable pour extraire la structure — c’est l’objet pedagogique de ce notebook.
Le notebook frere Argumentation-Onto-02-CrossLinks-CSV-Python.ipynb (PR-B) lit le CSV canonique ; les deux substances (OWL et CSV) ont ete reconciliees par #763 (crossLink + AIF desormais emis dans l’OWL).
Source Argumentum — ce notebook lit l’OWL reellement commite ontologies/argumentum_fallacies.owl (idiome annotation-space #763). Le CSV canonique des cross-links et sa methode de recollement vivent dans le notebook frere Argumentation-Onto-02-CrossLinks-CSV-Python.ipynb ; les definitions officielles citees ci-dessous (predicats definition / example) proviennent du meme OWL. EPIC source : #4960 (Volet A), issue #17557.
# --- Imports + parseur regex maison de l'OWL2/XML ---from pathlib import Pathimport reOWL_PATH = Path("ontologies/argumentum_fallacies.owl").resolve()print(f"Chargement de : {OWL_PATH.name}") # basename, jamais chemin absolu machineprint(f"Existe : {OWL_PATH.exists()} (taille = {OWL_PATH.stat().st_size:,} octets)")owl_text = OWL_PATH.read_text(encoding="utf-8")print(f"Longueur du texte : {len(owl_text):,} caracteres, {owl_text.count(chr(10)) +1:,} lignes")# Axiomes de cardinalite : desormais bien formes (avec onProperty) -- ils ne sont# plus le point de rupture (l'ancien OWL avait 37 axiom sans onProperty ligne ~4090).n_obj_exact =len(re.findall(r"<ObjectExactCardinality", owl_text))n_data_exact =len(re.findall(r"<DataExactCardinality", owl_text))print(f"\nCardinalite : ObjectExactCardinality={n_obj_exact}, DataExactCardinality={n_data_exact} (bien formes).")# Test rdflib : echoue toujours sur ce dialecte, mais pour une autre cause qu'avant #763.try:import rdflibtry: g = rdflib.Graph(); g.parse(str(OWL_PATH))print(f"rdflib : OK inattendu ({len(g):,} triples)")exceptExceptionas e:print(f"rdflib : installe mais ECHEC au parse -> {type(e).__name__}: {str(e)[:80]}")exceptImportError:print("rdflib : non installe dans ce kernel (test skippe) -- historiquement il echoue sur ce dialecte.")print("owlready2 : charge le fichier mais expose 0 classe via l'API Python (idiome annotation-space).")print("\n=> On conserve le parseur regex tolerant : chemin fiable pour extraire la structure.")
Chargement de : argumentum_fallacies.owl
Existe : True (taille = 5,993,635 octets)
Longueur du texte : 5,881,884 caracteres, 96,093 lignes
Cardinalite : ObjectExactCardinality=38, DataExactCardinality=0 (bien formes).
rdflib : installe mais ECHEC au parse -> TypeError: sequence item 0: expected str instance, NoneType found
owlready2 : charge le fichier mais expose 0 classe via l'API Python (idiome annotation-space).
=> On conserve le parseur regex tolerant : chemin fiable pour extraire la structure.
Lecture : le fichier OWL pese ~6,0 MB (mesure exacte — octets et lignes — dans la sortie de la cellule 4 de ce run). Les axiomes ObjectExactCardinality (38) sont desormais bien formes (avec onProperty) — ce ne sont plus les « 37 axiom casses ligne 4090 » de l’ancien snapshot. Malgre cela, rdflib echoue toujours (autre cause : le dialecte OWL2/XML avec annotations-resource) et owlready2 charge mais n’expose rien via son API Python. Le parseur regex tolerant reste donc l’approche retenue : il extrait Class, ObjectProperty et AnnotationAssertion directement du XML, sans dependre d’un moteur RDF.
3. Structure : Class + ObjectProperty + AnnotationAssertion
Depuis #763, l’ontologie Argumentum suit un idiome annotation-space :
Couche
Pattern OWL2
Compte
Rôle
Concepts
Declaration/Class
1 532 (+ 4 AIF)
Un owl:Class par sophisme + RA/I/CA-node + Conflict
Normalisation, puis retour partiel de l’ancien idiome. Le socle #763 reste 1 owl:Class par sophisme portant ses labels en annotations — d’ou la chute du nombre de concepts (10 976 → 1 532), en réalité une normalisation (plus de doublons par langue). Mais l’idiome individus n’a jamais disparu du fichier livré : la resynchronisation du 30/08 (état amont c86b71ff2, Argumentum #1231 — « les arêtes du graphe argumentatif deviennent raisonnables ») a réintroduitNamedIndividual et ObjectPropertyAssertion (4 244 et 2 122 à l’état 30/08 ; l’épingle courante 6bc573f en porte la forme consolidée : 1 760 et 880) ; seul ClassAssertion reste à 0. Les relations concept→concept, elles, restent portées par les AnnotationAssertion (§5).
Nouveaute #763 : l’AIF n’est plus seulement declaree, elle type les sophismes — 145 concepts portent aifAttackedNode → RA/I/CA-node (§5-6).
# --- Comptage de la structure : Class + ObjectProperty + AnnotationAssertion ---from collections import Counter# Concepts = Declaration/Class (le generateur #763 modelise 1 owl:Class par sophisme).class_decl_pattern = re.compile(r'<Declaration>\s*<Class IRI="([^"]+)"\s*/>\s*</Declaration>')class_decls = class_decl_pattern.findall(owl_text)aif_classes = [c for c in class_decls if'arg.dundee.ac.uk/aif'in c]arg_classes = [c for c in class_decls if'argumentum_fallacies'in c]print(f"Declarations owl:Class : {len(class_decls):,}")print(f" - concepts Argumentum : {len(arg_classes):,}")print(f" - classes AIF : {len(aif_classes)}{[c.split('#')[-1] for c in aif_classes]}")# Predicats = Declaration/ObjectProperty (verbes crossLink + AIF).op_pattern = re.compile(r'<Declaration>\s*<ObjectProperty IRI="([^"]+)"\s*/>\s*</Declaration>')obj_props = op_pattern.findall(owl_text)print(f"\nObjectProperty declarees : {len(obj_props)}")print(f" {[o.split('#')[-1] for o in obj_props]}")# Ancien idiome (individus) -- desormais ABSENT.n_named =len(re.findall(r'<NamedIndividual\s', owl_text))n_class_assertion =len(re.findall(r'<ClassAssertion', owl_text))n_opa =len(re.findall(r'<ObjectPropertyAssertion', owl_text))print(f"\nAncien idiome (individus), desormais absent :")print(f" NamedIndividual : {n_named}")print(f" ClassAssertion : {n_class_assertion}")print(f" ObjectPropertyAssertion : {n_opa}")# Relations = AnnotationAssertion (resource = concept->concept, literal = valeur textuelle).aa_resource = re.compile(r'<AnnotationAssertion>\s*<AnnotationProperty IRI="([^"]+)"\s*/>\s*<IRI>([^<]+)</IRI>\s*<IRI>([^<]+)</IRI>\s*</AnnotationAssertion>')aa_literal = re.compile(r'<AnnotationAssertion>\s*<AnnotationProperty IRI="([^"]+)"\s*/>\s*<IRI>([^<]+)</IRI>\s*<Literal(?:\s+[^>]*)?>([^<]*)</Literal>\s*</AnnotationAssertion>')AA_RES = aa_resource.findall(owl_text) # (predicate_iri, subject_iri, object_iri)AA_LIT = aa_literal.findall(owl_text) # (predicate_iri, subject_iri, literal_value)print(f"\nAnnotationAssertion : {len(AA_RES) +len(AA_LIT):,} (resource={len(AA_RES):,}, literal={len(AA_LIT):,})")# Compteurs conserves pour les sections suivantes.N_CLASSES =len(class_decls)N_ARG_CLASSES =len(arg_classes)N_OBJ_PROP =len(obj_props)
Lecture : le coeur de l’ontologie reste fait de 1 532 owl:Class (concepts Argumentum) + 4 classes AIF, relies par 6 632 AnnotationAssertion-resource et decrits par 10 590 AnnotationAssertion-literal. #763 a bien bascule d’un modèle individus + triplets ObjectProperty vers un modèle classes + annotation-space pour les relations concept→concept — mais l’ancien idiome est revenu partiellement avec la resynchronisation du 30/08 (Argumentum #1231) : le fichier livré porte toujours cet idiome individus — 1 760 NamedIndividual et 880 ObjectPropertyAssertion à l’épingle courante (4 244 et 2 122 à l’état 30/08), seul ClassAssertion restant a 0. Les 10 ObjectProperty declarees (8 verbes crossLink + hasConflictedElement + aifAttackedNode) restent declarees comme proprietes mais emises comme annotations pour la couche concept→concept (contrainte de l’adapter OWLSharp).
4. Schemes de Walton : classes materialisees + labels
Les schemes d’argumentation (Walton, Reed & Macagno 2008) sont des patterns d’inference reconnus en logique informelle. Le générateur #763 materialise les mappings AIF_skos* du CSV en 100 classes de forme *_Inference_* / *_Conflict (ex. PopularOpinion_Inference_Conflict, Example_Inference_Conflicted, CauseToEffect_Inference). On les repere directement dans les declarations de Class, en complement de la detection classique par substring dans les skos:prefLabel (2 816 labels = 1 408 FR + 1 408 EN).
# --- Labels multilingues (skos:prefLabel) + schemes de Walton ---from collections import Counter# prefLabel : AnnotationProperty IRI complet (plus d'abbreviatedIRI depuis #763).pref_pattern = re.compile(r'<AnnotationAssertion>\s*<AnnotationProperty IRI="http://www\.w3\.org/2004/02/skos/core#prefLabel"\s*/>\s*<IRI>([^<]+)</IRI>\s*<Literal(?:\s+xml:lang="([^"]+)")?[^>]*>([^<]*)</Literal>\s*</AnnotationAssertion>')pref_matches = pref_pattern.findall(owl_text)print(f"skos:prefLabel extraits : {len(pref_matches):,}")print(f"Langues : {dict(Counter(l.upper() for _, l, _ in pref_matches if l))}")# dict {localname: {iri, prefLabel:[...]}}taxonomy = {}for iri, lang, label in pref_matches: local = iri.rstrip('#').split('#')[-1].split('/')[-1]ifnot local:continue entry = taxonomy.setdefault(local, {'iri': iri, 'prefLabel': []})if label: entry['prefLabel'].append(label)print(f"Concepts distincts (par localname) : {len(taxonomy):,}")# Schemes Walton materialises en classes (#763) -- signal robuste, sans faux positifs de substring.walton_shaped =sorted(c.split('#')[-1] for c in arg_classes if'_Inference'in c or'_Conflict'in c)print(f"\nClasses de forme Walton-scheme (materialisation AIF_skos) : {len(walton_shaped)}")for c in walton_shaped[:8]:print(f" - {c}")# Detection complementaire par substring dans les prefLabel.WALTON_SCHEMES = ["Position to Know", "Sign", "Cause to Effect", "Analogy", "Expert Opinion", "Popular Opinion"]walton_found = {k: [] for k in WALTON_SCHEMES}for local, entry in taxonomy.items():for label in entry.get('prefLabel', []):for k in WALTON_SCHEMES:if k.lower() in label.lower(): walton_found[k].append((local, label));breakprint("\nSchemes Walton detectes dans les prefLabel :")for k in WALTON_SCHEMES: hits = walton_found[k]print(f" {'v'if hits else'x'}{k:18s} : {len(hits)} occurrence(s)")
skos:prefLabel extraits : 2,816
Langues : {'FR': 1408, 'EN': 1408}
Concepts distincts (par localname) : 1,312
Classes de forme Walton-scheme (materialisation AIF_skos) : 123
- Analogy_Inference_Conflict
- Analogy_Inference_Conflicted
- ArbitraryVerbalClassification_Inference_Conflict
- ArbitraryVerbalClassification_Inference_Conflicted
- CausalSlipperySlope_Inference_Conflict
- CausalSlipperySlope_Inference_Conflicted
- CauseToEffect_Inference_Conflict
- CauseToEffect_Inference_Conflicted
Schemes Walton detectes dans les prefLabel :
x Position to Know : 0 occurrence(s)
v Sign : 11 occurrence(s)
x Cause to Effect : 0 occurrence(s)
v Analogy : 4 occurrence(s)
x Expert Opinion : 0 occurrence(s)
x Popular Opinion : 0 occurrence(s)
Lecture : 2 816 skos:prefLabel (1 408 FR + 1 408 EN, soit 1 label par sophisme et par langue — la normalisation attendue du nouvel idiome). La detection des schemes de Walton s’appuie desormais sur les 100 classes materialisees (*_Inference_*), signal bien plus robuste que la recherche par substring dans les labels (qui produisait des faux positifs sur « Sign », « Rule »…). La detection par substring est conservee a titre de comparaison pedagogique.
4bis. La mindmap en un coup d’œil
La promesse d’un notebook d’ontologie est de montrer la carte, pas seulement de la compter. Cette section montre deux échelles de la même carte : d’abord la mindmap originale produite par le dépôt Argumentum (ci-dessous, importée telle quelle — la vue complète de la taxonomie, issue de la chaîne de production amont décrite dans l’encadré Source Argumentum en fin de section), puis un zoom pédagogique restreint au noyau, lisible en un écran, avec les liens transverses colorés par famille de prédicat.
Pour le zoom : avant d’auditer les relations (§5), visualisons donc le noyau pedagogique du scheme : la racine fallacy, ses 7 branches principales (Tricherie, Erreur de raisonnement, Influence, Insufficiency, Erreur mathematique, Abus de langage, Obstruction) et leurs enfants directs. Mapper les 1 532 sophismes serait un exercice d’integralite, pas une introduction.
Choix d’outil (Chantier C de l’issue) : pyvis produit du HTML interactif embarque en iframe — non rendu par GitHub ni par la galerie README ; networkx + matplotlib produisent un SVG statique rendu partout. Pour un arbre regulier, un layout manuel en colonnes reste plus lisible qu’un spring_layout ; networkx reste la brique des graphes generiques (cf. exercices 2 et 4). On retient donc matplotlib exporte en SVG (option A2 : fichier externe out/argumentum_mindmap.svg charge par display(SVG(...)) — conforme regle H, aucun chemin machine dans les outputs).
Code couleur des aretes — les trois couches de l’annotation-space #763 :
Couleur
Famille de predicat
Exemples
gris
SKOS hierarchique
broader / narrower
orange
cross-links transverses
mirrors, isRelatedTo, leverages
violet
couche AIF
aifAttackedNode (cibles RA/I/CA)
# --- La mindmap au pin courant : variantes disponibles + carte interactive ---# Re-pin #17669 : cette cellule (stub preserve-rails depuis PR #18502) affiche# desormais la carte du pin du sous-module. Jamais de retour a l'export brut# Fallacies_fr.svg sans attribut de noeud (regle SOTA, PR #18387) : la carte de# reference de ce notebook est l'INTERACTIVE (svg-pan-zoom), la variante d'etude# est Fallacies_fr.links.svg (liens transverses en haut contraste).from pathlib import Pathfrom IPython.display import IFrame, displaySUBM = Path("Argumentum")MM = SUBM /"Cards"/"Fallacies"/"Mindmaps"/"fr"variants = [ ("export brut (reference, non affiche)", "Fallacies_fr.svg"), ("variante d'etude : liens transverses", "Fallacies_fr.links.svg"), ("variante attribuee : noeuds", "Fallacies_fr.content.svg"), ("carte interactive (svg-pan-zoom)", "Fallacies_cards_fr.html"),]print("Variantes de la mindmap FR au pin du sous-module :")for label, name in variants: p = MM / nameif p.exists():print(f" {label:38s}{name:26s}{p.stat().st_size:>12,} octets")else:print(f" {label:38s}{name:26s} ABSENTE (sous-module non initialise)")interactive = MM /"Fallacies_cards_fr.html"if interactive.exists(): display(IFrame(interactive.as_posix(), width="100%", height=620))else:print("Sous-module absent : git submodule update --init pour ouvrir la carte interactive.")
Variantes de la mindmap FR au pin du sous-module :
export brut (reference, non affiche) Fallacies_fr.svg 2,986,577 octets
variante d'etude : liens transverses Fallacies_fr.links.svg 1,857,588 octets
variante attribuee : noeuds Fallacies_fr.content.svg 2,629,983 octets
carte interactive (svg-pan-zoom) Fallacies_cards_fr.html 6,137,419 octets
Guide — les deux variantes post-traitées du traitement par Argumentum. Le stub code c.13 ne produit aucune carte : la mindmap de référence est exportée par le sous-module ArgumentumGames/Argumentum (sous-module épinglé) au pin du 04/10 (6bc573f, incluant la de-curation 9e765e4b). Le dépôt upstream produit deux variantes HTML interactives de la même carte, à choisir selon le contexte d’usage : | Variante | Chemin submodule | Mode d’intégration | Idéal pour | |—|—|—|—| | Fallacies_cards_fr.html (included) | Cards/Fallacies/Mindmaps/fr/Fallacies_cards_fr.html | SVG inline + svg-pan-zoom + Hammer pinch | Notebook Jupyter local | | Fallacies_cards_fr_ext.html (external) | Cards/Fallacies/Mindmaps/fr/Fallacies_cards_fr_ext.html | SVG via <object type="image/svg+xml"> | Publication web, lazy-loading |
Pour ouvrir une variante depuis ce notebook :
from pathlib import Pathimport IPython.display as DSUBM = Path('Argumentum')var = SUBM /'Cards/Fallacies/Mindmaps/fr/Fallacies_cards_fr.html'D.IFrame(var.as_posix(), width='100%', height=620)
Millésime. La cellule c.15 qui suit reste Source Argumentum : elle vaut sur les brochures upstream, pas sur la carte visuelle (qui n’est plus dans ce notebook depuis PR #18502 — la cellule c.13 est un stub preserves-rails, ce qui confirme la voie d’export interactive).
Source Argumentum — voir le guide adjacent (cellule guide-4bis-variants) pour les deux variantes HTML interactives de la carte ; ce qui suit décrit la chaîne de production de l’artefact original produit par le dépôt ArgumentumGames/Argumentum (sous-module épinglé de ce repo, dossier Cards/Fallacies/Mindmaps/fr/). Sa chaîne de production réelle :
.content.svg (nœuds attribués) · .links.svg (liens transverses en haut contraste)
oui
Cards/Fallacies/Mindmaps/fallacy_map.mmn’est pas cette source : 302 octets, 4 nœuds anglais, aucun code ne le lit (bouchon de test). Les .mm réels vivent dans Generation/Converters/Argumentum.AssetConverter/Data/Mindmap/ (650–808 Ko). Le dossier porte 8 langues (ar en es fa fr pt ru zh) et 2 gabarits racine (external.html, included.html).
Millésime — carte et OWL au même pin. Depuis la passe de re-pin (#17669), l’OWL que lit ce notebook (ontologies/argumentum_fallacies.owl, resynchronisé verbatim à 6bc573f, 04/10) et la carte (exportée du même pin) décrivent le même état des liens transverses : 735 cross-links, dont mirrors réduit à 4 triplets — la seule paire goldenMeanFallacy ↔︎ falseDilemma (dé-curation amont #1286/#1247, détaillée en §5.1). Les compteurs de ce notebook décrivent donc l’état dé-curé, plus l’état 30/08 qui portait 720 mirrors. À cette échelle la carte (variantes HTML interactives, voir guide) fait autorité sur la forme, l’OWL sur les chiffres — et le zoom génératif de la section suivante est là pour orienter la lecture, pas pour la remplacer : l’originale fait autorité, le zoom fait comprendre.
Épingles successives (historique). Trois millésimes se sont succédé : l’OWL vendoré 30/08 (ae84c91c via #13554, 720 mirrors, 1 977 cross-links), l’épingle 26/09 (7cf48616, déjà dé-curée côté amont : 4 mirrors, 735 liens — la carte committée datait alors encore du pin 17/09 0ab05d66), et l’épingle courante 6bc573f où cette resynchronisation aligne l’OWL et la carte au même millésime. Les couches stables (aifAttackedNode 145, hiérarchie SKOS) sont identiques sur les trois.
# --- Mindmap SVG du noyau Argumentum (option A2 : fichier externe) ---from collections import defaultdict, Counterfrom pathlib import Pathimport matplotlibmatplotlib.use("Agg")import matplotlib.pyplot as pltfrom IPython.display import SVG, displayOUT_DIR = Path("out")OUT_DIR.mkdir(exist_ok=True)MINDMAP_SVG = OUT_DIR /"argumentum_mindmap.svg"def _loc(x):return x.rstrip("#").split("#")[-1].split("/")[-1]# Labels FR/EN reconstruits depuis pref_matches (cellule labels, rien de re-parse).label_by = defaultdict(dict)for _iri, _lang, _lab in pref_matches:if _lab: label_by[_loc(_iri)][(_lang or"").lower()] = _labdef fr(node):return label_by.get(node, {}).get("fr", node)# Arbre SKOS : narrower(sujet -> objet) = sujet est parent.children = defaultdict(list)for _p, _s, _o in AA_RES:if _loc(_p) =="narrower": children[_loc(_s)].append(_loc(_o))root ="fallacy"branches =sorted(set(children.get(root, [])))visible = {root} |set(branches)for b in branches: visible |=set(children.get(b, []))# Liens transverses REELS entre noeuds visibles (couche cross-link).TRANSVERSE = ("mirrors", "isRelatedTo", "leverages", "inverts", "allows", "opposes")transverse_edges = []for _p, _s, _o in AA_RES:if _loc(_p) in TRANSVERSE and _loc(_s) in visible and _loc(_o) in visible: transverse_edges.append((_loc(_s), _loc(_p), _loc(_o)))# Couche AIF : distribution des cibles d'attaque (cohérente avec §7).aif_target_ct = Counter( _loc(_o) for _p, _s, _o in AA_RES if _loc(_p) =="aifAttackedNode")# ---- Layout manuel : racine en haut, 7 branches, petits-enfants dessous.fig, ax = plt.subplots(figsize=(17, 10))B =len(branches)pos = {root: (0.0, 1.02)}for i, b inenumerate(branches): xb = (i - (B -1) /2) *3.0 pos[b] = (xb, 0.62) kids =sorted(set(children.get(b, [])))for j, k inenumerate(kids): pos[k] = (xb + (j - (len(kids) -1) /2) *1.05, 0.30)# Aretes SKOS (gris) puis transverses (orange).for parent in [root] + branches:for k in children.get(parent, []):if k in pos: ax.plot([pos[parent][0], pos[k][0]], [pos[parent][1], pos[k][1]], color="#9e9e9e", lw=1.2, zorder=1)for s, p, o in transverse_edges: ax.plot([pos[s][0], pos[o][0]], [pos[s][1], pos[o][1]], color="#e67e22", lw=2.0, zorder=2)# Noeuds : racine, branches, feuilles de niveau 2.ax.annotate(fr(root), pos[root], ha="center", va="center", fontsize=13, fontweight="bold", color="white", bbox=dict(boxstyle="round,pad=0.45", fc="#37474f", ec="none"), zorder=3)for b in branches: n_kids =len(children.get(b, [])) ax.annotate(f"{fr(b)}\n({n_kids} enf.)", pos[b], ha="center", va="center", fontsize=10.5, color="white", bbox=dict(boxstyle="round,pad=0.35", fc="#546e7a", ec="none"), zorder=3)for k in [k for b in branches for k in children.get(b, [])]: ax.annotate(fr(k), pos[k], ha="center", va="center", fontsize=8.2, color="#263238", bbox=dict(boxstyle="round,pad=0.25", fc="#cfd8dc", ec="#90a4ae"), zorder=3)# Couche AIF (violet) : panneau lateral avec les cibles d'attaque reelles.badge_x = (B -1) /2*3.0+2.6badges = [("RA-node", aif_target_ct.get("RA-node", 0), "undercut"), ("I-node", aif_target_ct.get("I-node", 0), "undermine"), ("CA-node", aif_target_ct.get("CA-node", 0), "rebut")]for yy, (node, n, sense) inzip((0.78, 0.62, 0.46), badges): ax.annotate(f"{node}\n{n} attaques ({sense})", (badge_x, yy), ha="center", va="center", fontsize=10, color="white", bbox=dict(boxstyle="round,pad=0.35", fc="#6a1b9a", ec="none"), zorder=3)ax.plot([badge_x -1.15, badge_x -1.15], [0.40, 0.84], color="#6a1b9a", lw=1.6, ls="--", zorder=1)ax.annotate("aifAttackedNode\n(depuis les feuilles)", (badge_x -1.35, 0.62), ha="right", va="center", fontsize=9, color="#6a1b9a", rotation=90)from matplotlib.lines import Line2Dlegend_items = [ Line2D([0], [0], color="#9e9e9e", lw=2, label="SKOS : broader / narrower"), Line2D([0], [0], color="#e67e22", lw=2, label="cross-links : mirrors / isRelatedTo / ..."), Line2D([0], [0], color="#6a1b9a", lw=2, ls="--", label="AIF : aifAttackedNode (RA/I/CA)"),]ax.legend(handles=legend_items, loc="lower left", fontsize=9, frameon=True)ax.set_title(f"Mindmap Argumentum — noyau pedagogique : {len(visible)} noeuds visibles "f"(sur {N_ARG_CLASSES:,} classes)", fontsize=13, fontweight="bold")ax.set_xlim((B -1) /2*-3.0-2.0, badge_x +2.0)ax.set_ylim(0.12, 1.14)ax.axis("off")plt.tight_layout()fig.savefig(str(MINDMAP_SVG), format="svg", bbox_inches="tight")plt.close(fig)plt.close("all")matplotlib.use("module://ipykernel.pylab.backend_inline")print(f"SVG ecrit : {MINDMAP_SVG.name} ({MINDMAP_SVG.stat().st_size:,} octets)")n_tr =len(transverse_edges)print(f"Liens transverses DIRECTS entre noeuds visibles du noyau : {n_tr}")print(" (sur les 735 cross-links de l'OWL resynchronise, la couche transverse relie")print(" surtout les feuilles profondes — cf. la paire survivante en section 5.1)")print(f"Couche AIF sur l'ontologie entiere : "f"RA={aif_target_ct.get('RA-node', 0)}, I={aif_target_ct.get('I-node', 0)}, "f"CA={aif_target_ct.get('CA-node', 0)} — detail en section 7")display(SVG(filename=str(MINDMAP_SVG)))
SVG ecrit : argumentum_mindmap.svg (127,766 octets)
Liens transverses DIRECTS entre noeuds visibles du noyau : 1
(sur les 735 cross-links de l'OWL resynchronise, la couche transverse relie
surtout les feuilles profondes — cf. la paire survivante en section 5.1)
Couche AIF sur l'ontologie entiere : RA=87, I=53, CA=5 — detail en section 7
Lecture — la carte montre d’un coup d’œil les trois couches de l’annotation-space #763 : l’arbre SKOS en gris (la taxonomie Walton-Argumentum), la couche transverse en orange — 1 seul lien direct mesuré entre noeuds du noyau (sur les 735 cross-links de l’OWL resynchronisé au pin courant : la couche transverse relie surtout les feuilles profondes, §5.1 en montre la paire survivante), et la couche AIF en violet (les 145 attaques partent des 1 532 concepts vers trois types de cibles). La suite du notebook descend précisément dans ces deux couches basses : §5.1 pour la paire mirrors réelle, §7 pour les attaques.
5. Relations : inventaire des AnnotationAssertion-resource
Depuis #763, les relations concept→concept sont portees par des AnnotationAssertion-resource (et non plus des ObjectPropertyAssertion). L’inventaire par predicat revele trois familles :
Inversion de la note historique : l’ancien OWL n’incluait PAS les 8 verbes crossLink (ils etaient CSV-only). #763 les a cables et emis — ce notebook les inventorie donc desormais dans l’OWL.
Millésime §5 : ces comptes décrivent la copie vendoréeontologies/argumentum_fallacies.owlresynchronisée au pin 6bc573f du sous-module (passe de re-pin #17669) — le même millésime que la carte (§4bis). L’état précédent (30/08, ae84c91c via #13554) portait 1 977 cross-links dont 720 mirrors ; la dé-curation amont (#1286/#1247) a retiré ce qui dupliquait l’arbre et ne garde de mirrors que la paire goldenMeanFallacy ↔︎ falseDilemma (§5.1). Les couches stables n’ont pas bougé : aifAttackedNode 145, hiérarchie 1 407/1 407/1 408/1 409.
# --- Inventaire des relations (AnnotationAssertion-resource) par predicat ---from collections import Counterrel_usage = Counter()for pred_iri, _subj, _obj in AA_RES: rel_usage[pred_iri.rstrip('#').split('#')[-1].split('/')[-1]] +=1print("Relations (AnnotationAssertion-resource) par predicat :")print(f" {'Predicat':22s} | Count")print(" "+"-"*34)for pred, n in rel_usage.most_common(20):print(f" {pred:22s} | {n:>5d}")print(f"\n Total predicats distincts : {len(rel_usage)}")# Les 8 verbes crossLink SONT desormais dans l'OWL (#763) -- inversion de la note historique.CROSSLINK_VERBS = ['predatesOn', 'denounces', 'leverages', 'allows', 'opposes', 'inverts', 'mirrors', 'isRelatedTo']n_crosslink =sum(rel_usage[v] for v in CROSSLINK_VERBS)print(f"\n=> crossLink dans l'OWL : {n_crosslink:,} AnnotationAssertion")print(f" (mirrors={rel_usage['mirrors']}, isRelatedTo={rel_usage['isRelatedTo']}, "f"leverages={rel_usage['leverages']}, inverts={rel_usage['inverts']}, ...)")print(f" AIF attack : aifAttackedNode={rel_usage['aifAttackedNode']} (resource -> RA/I/CA-node)")print(f" mapping Walton : broadMatch={rel_usage['broadMatch']}, closeMatch={rel_usage['closeMatch']}, "f"narrowMatch={rel_usage['narrowMatch']}")print("\n NOTE : contrairement a l'ancien OWL (crossLink ABSENT), #763 a cable ces 8 verbes.")
Lecture : l’inventaire par predicat montre la structure reelle de la taxonomie. La colonne vertebrale hiérarchique (broader/narrower/inScheme/type, ~1 408 chacune = une par sophisme) est desormais doublee d’une couche transverse crossLink (735 relations, dominée par leverages (327) et isRelatedTo (172) — mirrors n’y est plus qu’un résidu de 4 triplets, §5.1) et d’une couche AIF attack (145 aifAttackedNode). C’est le changement de fond apporte par #763 : la taxonomie n’est plus un simple arbre, mais un graphe hiérarchie + transverse + AIF.
5.1 mirrors après la dé-curation : de 720 triplets à la seule paire qui les méritait
L’inventaire ci-dessus montre mirrors à 4 triplets — un résidu. Ce n’était pas toujours le cas : l’état 30/08 vendoré en portait 720, verbe crossLink dominant loin devant isRelatedTo (642) et leverages (402). La dé-curation amont (#1286/#1247) a retiré ce que l’audit de cette section mesurait comme de la duplication de l’arbre : à 99,4 %, un mirrors reliait deux sophismes de même parent broader — l’arbre le disait déjà. Ce qui reste au pin courant est exactement l’exception : la seule paire qui traverse la hiérarchie.
Pourquoi garder un audit sur 4 triplets ? Parce que la question pédagogique reste entière : que modélise mirrors exactement, et comment se distingue-t-il de la colonne vertébrale hiérarchique broader/narrower ? Contrairement à la hiérarchie SKOS — orientée (un sophisme est plus étroit que un autre, narrower ≠ réciproque de broader au niveau de l’assertion) — mirrors est déclaré symétrique : si A mirror B, alors B mirror A. Vérifions-le sur la paire survivante, et mesurons ce qu’elle traverse.
# --- Audit du predicat mirrors (etat de-cure) : symetrie + la paire survivante ---from collections import Countermirrors = [(subj_iri.rstrip("#").split("#")[-1].split("/")[-1], obj_iri.rstrip("#").split("#")[-1].split("/")[-1])for pred_iri, subj_iri, obj_iri in AA_RESif pred_iri.rstrip("#").split("#")[-1].split("/")[-1] =="mirrors"]n =len(mirrors)pair_set =set(mirrors)n_symmetric =sum(1for a, b in mirrors if (b, a) in pair_set)print(f"mirrors : {n} triplets au total (etat de-cure au pin courant)")print(f" symetriques (a->b ET b->a presents) : {n_symmetric}/{n} ({n_symmetric/n:.0%})")def _loc(x):return x.rstrip("#").split("#")[-1].split("/")[-1]_lab = {}for _iri, _lang, _labv in pref_matches:if _labv: _lab.setdefault(_loc(_iri), {})[(_lang or"").lower()] = _labv_par = {}for _p, _s, _o in AA_RES:if _loc(_p) =="broader": _par.setdefault(_loc(_s), set()).add(_loc(_o))# Exhaustif : le predicat ne porte plus qu'une paire, on liste tout.src_count = Counter(s for s, _ in mirrors)print("\nEmetteurs (exhaustif) :")for name, _c insorted(src_count.items()): _l = _lab.get(name, {}) _pfr =sorted(_lab.get(_q, {}).get("fr", _q) for _q in _par.get(name, ()))print(f" {name:<22s}{_l.get('fr', name)} / {_l.get('en', name)}"f" <- {', '.join(_pfr) if _pfr else'(racine)'}")for _t insorted(o for s, o in mirrors if s == name): _tl = _lab.get(_t, {}) _tfr =sorted(_lab.get(_q, {}).get("fr", _q) for _q in _par.get(_t, ()))print(f" -> {_t:<22s}{_tl.get('fr', _t)} / {_tl.get('en', _t)}"f" <- {', '.join(_tfr) if _tfr else'(racine)'}")# Transversalite : les aretes uniques relient-elles des parents SKOS differents ?def _parents(x):return _par.get(x, set())unique_pairs = {frozenset((a, b)) for a, b in mirrors if a != b}n_transverse =sum(1for pr in unique_pairsifall(_parents(m) for m in pr)and _parents(next(iter(pr))).isdisjoint(_parents(next(iter(pr - {next(iter(pr))})))))n_fratrie =len(unique_pairs) - n_transverseprint(f"\nAretes uniques (paires de concepts) : {len(unique_pairs)}")print(f" entre parents SKOS DIFFERENTS (transverses) : {n_transverse}")print(f" entre memes parents (fratries, duplication d'arbre) : {n_fratrie}")print("\n=> Avant la de-curation (etat 30/08, 720 triplets) : 716/720 etaient des")print(" fratries ; l'amont a retire la duplication et garde la paire transverse.")
mirrors : 4 triplets au total (etat de-cure au pin courant)
symetriques (a->b ET b->a presents) : 4/4 (100%)
Emetteurs (exhaustif) :
falseDilemma Faux dilemme / False dilemma <- Sophisme des corrélatifs
-> goldenMeanFallacy Juste milieu / Golden mean fallacy <- Faux équilibre
-> goldenMeanFallacy Juste milieu / Golden mean fallacy <- Faux équilibre
goldenMeanFallacy Juste milieu / Golden mean fallacy <- Faux équilibre
-> falseDilemma Faux dilemme / False dilemma <- Sophisme des corrélatifs
-> falseDilemma Faux dilemme / False dilemma <- Sophisme des corrélatifs
Aretes uniques (paires de concepts) : 1
entre parents SKOS DIFFERENTS (transverses) : 1
entre memes parents (fratries, duplication d'arbre) : 0
=> Avant la de-curation (etat 30/08, 720 triplets) : 716/720 etaient des
fratries ; l'amont a retire la duplication et garde la paire transverse.
Lecture — la symétrie est par construction, et la paire survivante est la seule qui traversait l’arbre. Deux mesures, à ne pas confondre avec ce qu’elles semblent dire.
1. La symétrie n’est pas une régularité trouvée dans les données. Le générateur amont déclare mirrors symétrique et écrit l’arête inverse de chaque entrée du tableau : les 4 triplets ont leur réciproque par code, pas par observation (chaque direction est même écrite deux fois — 4 triplets pour une seule paire de concepts). Le contraste avec broader/narrower (orientés) reste juste — mais il ne s’appuie sur aucune découverte, et l’audit ci-dessus mesure donc une propriété du sérialiseur.
2. Ce qui reste est l’exception qui justifie la relation. À l’état 30/08, 716 des 720 triplets reliaient deux sophismes de même parent broader — mirrorsdupliquait l’arbre au lieu de le traverser (bow sous Formel avait 6 miroirs, tous sous Formel ; racism sous Discrimination en avait 5, tous sous Discrimination). La dé-curation amont (#1286/#1247) a retiré ces fratries et gardé une seule paire transverse : goldenMeanFallacy (Juste milieu, sous falseBalance) ↔︎ falseDilemma (Faux dilemme, sous fallacyOfCorrelatives) — deux parents différents, donc une arête que la hiérarchie ne dit pas. C’est le test opérationnel que la dé-curation applique à toute relation transverse : elle mérite sa place si elle relie ce que l’arbre ne relie pas. La cellule suivante dessine cette paire et ses deux parents SKOS.
Contraste avec le §7 (AIF attack) et le §8 (scheme-vs-conflict) : aifAttackedNode (145 resource) est bien, lui, une couche sémantique transverse — un sophisme y attaque une cible d’un autre type, hors de toute parenté. mirrors, réduit à sa paire survivante, ne redit plus l’arbre : il complète là où l’arbre s’arrête.
# --- La paire mirrors survivante : goldenMeanFallacy <-> falseDilemma (§5.1) ---from collections import defaultdictimport matplotlibmatplotlib.use("Agg")import matplotlib.pyplot as pltfrom pathlib import Pathfrom IPython.display import SVG, displaydef _loc(x):return x.rstrip("#").split("#")[-1].split("/")[-1]label_by = defaultdict(dict)for _iri, _lang, _lab in pref_matches:if _lab: label_by[_loc(_iri)][(_lang or"").lower()] = _labdef fr(n): return label_by.get(n, {}).get("fr", n)parents_of = defaultdict(set)for _p, _s, _o in AA_RES:if _loc(_p) =="broader": parents_of[_loc(_s)].add(_loc(_o))# La seule paire transverse que la de-curation a gardee (audit ci-dessus).PAIR = ("goldenMeanFallacy", "falseDilemma")mirrors_pairs = {frozenset((a, b)) for a, b in mirrors if a != b}assertfrozenset(PAIR) in mirrors_pairs, "la paire attendue n'est plus dans l'OWL"print(f"Paire mirrors survivante : {PAIR[0]} <-> {PAIR[1]} "f"({len(mirrors_pairs)} paire unique sur {len(mirrors)} triplets)")for n in PAIR:print(f" {n:<20s}{fr(n):<16s} <- parent SKOS : "f"{', '.join(sorted(fr(q) for q in parents_of[n]))}")communs = parents_of[PAIR[0]] & parents_of[PAIR[1]]print(f" parents communs : "f"{sorted(fr(q) for q in communs) if communs else'AUCUN (l arete traverse la hierarchie)'}")fig, ax = plt.subplots(figsize=(11, 6))cols = {PAIR[0]: -2.1, PAIR[1]: 2.1}for n, x in cols.items():for j, par inenumerate(sorted(parents_of[n])): ax.annotate(f"{fr(par)}\n({par})", (x, 1.7- j *0.9), ha="center", va="center", fontsize=10, color="#263238", bbox=dict(boxstyle="round,pad=0.35", fc="#eceff1", ec="#90a4ae"), zorder=3) ax.plot([x, x], [1.45- j *0.9, 0.32], color="#9e9e9e", lw=1.6, zorder=1) ax.annotate(f"{fr(n)}\n({n})", (x, 0.0), ha="center", va="center", fontsize=11, color="#263238", bbox=dict(boxstyle="round,pad=0.4", fc="#ffcc80", ec="#e67e22"), zorder=3)ax.plot([cols[PAIR[0]], cols[PAIR[1]]], [0.0, 0.0], color="#e67e22", lw=2.6, zorder=2)ax.annotate("mirrors (chaque direction ecrite deux fois)", (0.0, 0.30), ha="center", va="center", fontsize=10, color="#e65100")from matplotlib.lines import Line2Dax.legend(handles=[Line2D([0], [0], color="#9e9e9e", lw=2, label="SKOS : broader"), Line2D([0], [0], color="#e67e22", lw=2.6, label="crossLink : mirrors")], loc="lower center", fontsize=9, frameon=True)ax.set_title("La paire mirrors survivante : deux parents SKOS differents", fontsize=12, fontweight="bold")ax.set_xlim(-4.6, 4.6); ax.set_ylim(-0.9, 2.3); ax.axis("off")plt.tight_layout()OUT_DIR = Path("out"); OUT_DIR.mkdir(exist_ok=True)PAIR_SVG = OUT_DIR /"argumentum_mirrors_survivant.svg"fig.savefig(str(PAIR_SVG), format="svg", bbox_inches="tight")plt.close(fig)plt.close("all")matplotlib.use("module://ipykernel.pylab.backend_inline")print(f"SVG ecrit : {PAIR_SVG.name} ({PAIR_SVG.stat().st_size:,} octets)")display(SVG(filename=str(PAIR_SVG)))
Paire mirrors survivante : goldenMeanFallacy <-> falseDilemma (1 paire unique sur 4 triplets)
goldenMeanFallacy Juste milieu <- parent SKOS : Faux équilibre
falseDilemma Faux dilemme <- parent SKOS : Sophisme des corrélatifs
parents communs : AUCUN (l arete traverse la hierarchie)
SVG ecrit : argumentum_mirrors_survivant.svg (50,492 octets)
Lecture — la figure dit pourquoi cette paire a survécu à la dé-curation quand les autres arêtes sont parties : ses deux membres n’ont aucun parent SKOS commun. goldenMeanFallacy (Juste milieu) vit sous Faux équilibre (falseBalance), falseDilemma (Faux dilemme) sous Sophisme des corrélatifs (fallacyOfCorrelatives) : l’arête mirrors qui les relie est la seule de son prédicat que la hiérarchie ne redise pas. Le double trait orange rappelle la mesure ci-dessus : chaque direction est écrite deux fois par le sérialiseur (4 triplets pour 1 paire) — la symétrie est dans le code, pas dans les données. Une relation transverse mérite sa place quand elle relie ce que l’arbre ne relie pas ; celle-ci est le seul mirrors qui passe ce test.
6. Grappe Equivoque : sous-graphe type AIF
L’Equivoque (Equivocation) est un sophisme d’ambiguite : un même terme employe dans des sens différents au cours d’un même raisonnement. Dans l’ontologie, le concept semanticAmbiguity porte 9 relations (AnnotationAssertion-resource) :
AIF attack : aifAttackedNode → RA-node — le sophisme est formellement modelise comme une attaque de l’inference (RA-node = Rule-Application node au sens AIF, ce qui correspond a un undercut ASPIC+).
C’est la demonstration concrete de #763 : l’AIF est desormais utilisee pour typer les sophismes, la ou l’ancien OWL se contentait de declarer les nodes AIF sans les relier.
# --- Sous-graphe autour de semanticAmbiguity via AnnotationAssertion-resource ---# Noeuds lies a l'Equivoque (label contenant equivoc/quivoque).equiv_nodes = []for local, entry in taxonomy.items():for label in entry.get('prefLabel', []):if'quivoque'in label.lower() or'equivoc'in label.lower(): equiv_nodes.append((local, label, entry.get('iri')));breakprint(f"Noeuds lies a l'Equivoque : {len(equiv_nodes)}")for local, label, iri in equiv_nodes:print(f" - {local:30s} -> {label}")# Sous-graphe AnnotationAssertion-resource autour de semanticAmbiguity.TARGET ="semanticAmbiguity"print(f"\nSous-graphe (AnnotationAssertion-resource) autour de '{TARGET}' :")G_equiv = {}for pred_iri, subj_iri, obj_iri in AA_RES: subj = subj_iri.rstrip('#').split('#')[-1].split('/')[-1] obj = obj_iri.rstrip('#').split('#')[-1].split('/')[-1] pred = pred_iri.rstrip('#').split('#')[-1].split('/')[-1]if subj == TARGET or obj == TARGET: G_equiv[(subj, pred, obj)] = (pred_iri, subj_iri, obj_iri)print(f" Relations impliquees : {len(G_equiv)}")nodes =set([k[0] for k in G_equiv] + [k[2] for k in G_equiv])print(f" Noeuds impliques : {len(nodes)}")print("\nTriplets :")for (subj, pred, obj), _ in G_equiv.items(): tag =" <-- AIF attack"if pred =='aifAttackedNode'else""print(f" {subj[:32]:32s} --[{pred:16s}]--> {obj[:24]:24s}{tag}")
Lecture : le sous-graphe de semanticAmbiguity combine sa hiérarchie (parent ambiguity, enfants vagueness / polysemicLexicalShift / polysemyBySemanticChange) et son typage AIF (aifAttackedNode → RA-node). Les relations impliquées (15, réciproques SKOS comprises) melangent donc aretes taxonomiques, arete AIF, mapping Walton et lien transverse — c’est exactement la reconciliation que #763 apporte : une même entite porte simultanement sa place dans l’arbre SKOS et son rôle dans le modèle d’attaque AIF.
# --- §6 illlustre : definition officielle + sous-graphe SVG + les 3 enfants ---from collections import defaultdictimport matplotlibmatplotlib.use("Agg")import matplotlib.pyplot as pltfrom pathlib import Pathfrom IPython.display import SVG, displaydef _loc(x):return x.rstrip("#").split("#")[-1].split("/")[-1]label_by = defaultdict(dict)for _iri, _lang, _lab in pref_matches:if _lab: label_by[_loc(_iri)][(_lang or"").lower()] = _labdef fr(n): return label_by.get(n, {}).get("fr", n)literals = defaultdict(dict) # (node, pred) -> valeurfor _p, _s, _v in AA_LIT: literals[_loc(_s)][_loc(_p)] = _vTARGET ="semanticAmbiguity"print(f"=== {TARGET} — fiche officielle (AnnotationAssertion literal) ===")print(f"prefLabel FR : {fr(TARGET)}")print(f"definition : {literals.get(TARGET, {}).get('definition', '(absente)')}")print(f"example : {literals.get(TARGET, {}).get('example', '(absent)')[:220]}")# Les 3 enfants, expliques par leur definition officielle.children_of = defaultdict(list)for _p, _s, _o in AA_RES:if _loc(_p) =="narrower": children_of[_loc(_s)].append(_loc(_o))print("\n=== Les 3 enfants (sous-classes de l'Equivoque) ===")for c insorted(set(children_of.get(TARGET, []))): d = literals.get(c, {}).get("definition", "(absente)")print(f" - {c:30s} [{fr(c)}] : {d[:130]}")# ---- Sous-graphe SVG autour de semanticAmbiguity (G_equiv, calcule ci-dessus).SHOW_PREDS = {"broader", "narrower", "mirrors", "isRelatedTo", "type","aifAttackedNode", "leverages", "inverts"}edges = [(s, p, o) for (s, p, o) in G_equiv if p in SHOW_PREDS]nodes =sorted({s for s, _, _ in edges} | {o for _, _, o in edges})center = TARGETothers = [n for n in nodes if n != center]ang =2*3.14159*__import__("numpy").arange(len(others)) /max(len(others), 1)npos = {center: (0.0, 0.0)}for n, a inzip(others, ang): npos[n] = (3.3*__import__("numpy").cos(a), 3.3*__import__("numpy").sin(a))EDGE_STYLE = {"broader": ("#9e9e9e", "-"), "narrower": ("#9e9e9e", "-"),"mirrors": ("#e67e22", "-"), "isRelatedTo": ("#e67e22", "-"),"leverages": ("#e67e22", "-"), "inverts": ("#e67e22", "-"),"type": ("#b0bec5", ":"), "aifAttackedNode": ("#6a1b9a", "--"),}fig, ax = plt.subplots(figsize=(12.5, 9))for s, p, o in edges:if s in npos and o in npos: c, ls = EDGE_STYLE.get(p, ("#607d8b", "-")) ax.plot([npos[s][0], npos[o][0]], [npos[s][1], npos[o][1]], color=c, lw=1.7, ls=ls, zorder=2) mx, my = (npos[s][0] + npos[o][0]) /2, (npos[s][1] + npos[o][1]) /2 ax.annotate(p, (mx, my), fontsize=7.5, color=c, ha="center", va="center", bbox=dict(boxstyle="round,pad=0.12", fc="white", ec="none", alpha=0.85), zorder=4)ax.annotate(f"{fr(center)}\n({center})", npos[center], ha="center", va="center", fontsize=12, fontweight="bold", color="white", bbox=dict(boxstyle="round,pad=0.45", fc="#37474f", ec="none"), zorder=3)for n in others: short = n iflen(n) <=26else n[:24] +"…" ax.annotate(f"{fr(n)}\n({short})", npos[n], ha="center", va="center", fontsize=8.5, color="#263238", bbox=dict(boxstyle="round,pad=0.3", fc="#cfd8dc", ec="#90a4ae"), zorder=3)ax.set_title("Sous-graphe de semanticAmbiguity (graphe type AIF de la grappe Equivoque)", fontsize=12, fontweight="bold")ax.set_xlim(-4.6, 4.6); ax.set_ylim(-4.4, 4.4); ax.axis("off")plt.tight_layout()OUT_DIR = Path("out"); OUT_DIR.mkdir(exist_ok=True)EQUIV_SVG = OUT_DIR /"argumentum_equivoque_subgraph.svg"fig.savefig(str(EQUIV_SVG), format="svg", bbox_inches="tight")plt.close(fig)plt.close("all")matplotlib.use("module://ipykernel.pylab.backend_inline")print(f"\nSVG ecrit : {EQUIV_SVG.name} ({EQUIV_SVG.stat().st_size:,} octets)")display(SVG(filename=str(EQUIV_SVG)))
=== semanticAmbiguity — fiche officielle (AnnotationAssertion literal) ===
prefLabel FR : Équivoque
definition : Exploiting the different senses of a single word to make one's statement ambiguous.
example : That bear ate an avocado—or a lawyer.
=== Les 3 enfants (sous-classes de l'Equivoque) ===
- polysemicLexicalShift [Glissement lexical polysémique] : Using a polysemous word or one whose meaning has evolved, which may cause confusion.
- polysemyBySemanticChange [Changement sémantique] : A word is used whose meaning is unclear because its usual meaning is not the same as its original.
- vagueness [Expression vague] : Using terms so vague that they do not allow the audience to clearly understand what is meant.
SVG ecrit : argumentum_equivoque_subgraph.svg (83,328 octets)
Lecture — le dump de triplets devient une fiche + un graphe : la définition officielle de l’Équivoque (AnnotationAssertion definition, donc sourcée Argumentum, pas paraphrasée), puis le sous-graphe où l’on voit la double appartenance — le parent SKOS ambiguity (gris, hiérarchie), les trois enfants vagueness / polysemicLexicalShift / polysemyBySemanticChange (chacun incarnant une manière distincte de faire glisser le sens), et les liens transverses orange qui relient l’équivoque à d’autres familles. La grappe Equivoque est précisément un « sous-graphe type AIF » : un nœud, ses relations hiérarchiques ET ses relations transverses sur la même carte.
7. Distribution des attaques AIF par type de node
Le §5 a inventorié 145 aifAttackedNode (la couche AIF attack de l’OWL). Mais quel type d’attaque chaque sophisme porte-t-il ? En AIF (Argument Interchange Format), un node attaqué peut l’être à trois endroits distincts de la structure argumentative :
RA-node (Rule-Application) : l’attaque vise la règle d’inférence elle-même — en ASPIC+, c’est un undercut (le raisonnement est défaillant, indépendamment de la vérité des prémisses) ;
I-node (Information) : l’attaque vise une prémisse / information — c’est un undermine (la donnée invoquée est fausse ou douteuse) ;
CA-node (Conflict-Application) : l’attaque vise la conclusion — c’est un rebut (on conteste la conclusion elle-même, sans toucher au raisonnement qui y mène).
Compter la répartition de ces trois cibles révèle la structure dominante de la modélisation Argumentum : les sophismes attaquent-ils plutôt le raisonnement, la preuve, ou la conclusion ?
# --- Distribution des 145 aifAttackedNode par type de node AIF (RA / I / CA) ---from collections import Counteraif_target = Counter()for pred_iri, _subj, obj_iri in AA_RES: pred = pred_iri.rstrip("#").split("#")[-1].split("/")[-1]if pred =="aifAttackedNode": node_type = obj_iri.rstrip("#").split("#")[-1].split("/")[-1] aif_target[node_type] +=1total =sum(aif_target.values())print(f"aifAttackedNode total : {total}")print(f" RA-node (undercut, attaque l'inference) : {aif_target['RA-node']:>3d} ({aif_target['RA-node']/total:.0%})")print(f" I-node (undermine, attaque la premisse) : {aif_target['I-node']:>3d} ({aif_target['I-node']/total:.0%})")print(f" CA-node (rebut, attaque la conclusion) : {aif_target['CA-node']:>3d} ({aif_target['CA-node']/total:.0%})")
aifAttackedNode total : 145
RA-node (undercut, attaque l'inference) : 87 (60%)
I-node (undermine, attaque la premisse) : 53 (37%)
CA-node (rebut, attaque la conclusion) : 5 (3%)
Lecture : la modélisation AIF d’Argumentum est massivement dominée par l’undercut (RA-node = 87/145, ~60 %) — les sophismes attaquent le plus souvent la règle d’inférence (le raisonnement est défaillant), bien avant la prémisse (I-node = 53, undermine) et surtout la conclusion (CA-node = 5 seulement, rebut marginal). Cette structure (undercut ≫ undermine ≫ rebut) traduit l’orientation de la théorie de l’argumentation : un sophisme est d’abord un défaut de raisonnement, non pas simplement une conclusion fausse.
Lien avec le §6 : la grappe Equivoque (semanticAmbiguity) porte aifAttackedNode → RA-node — c’est bien un undercut (le sophisme d’équivoque attaque l’usage du terme dans l’inférence, pas la conclusion), ce que la distribution globale confirme comme le cas majoritaire. Le rebut (CA-node = 5) est rare : dans cette taxonomie, on conteste rarement une conclusion sans remettre en cause le raisonnement qui y mène.
Précaution méthodologique : ce compte (145) reflète l’état de l’OWL réellement commité dans CoursIA (ontologies/argumentum_fallacies.owl), resynchronisé par #13554 le 2026-08-30. L’exécution précédente de ce notebook (base #11598, 2026-08-18) mesurait 93 sur l’OWL d’avant resynchronisation ; ce nombre rapporté n’a plus cours — toujours fonder l’analyse sur l’OWL réellement présent, jamais sur un nombre hérité d’un état antérieur du sous-module.
Pourquoi le comptage brut sous-estime-t-il les attaques ? La distribution ci-dessus (87/53/5) compte les aifAttackedNodeexplicites : un seul prédicat, pointant vers un type de node. L’audit structurel Argumentum (méthode 3-axes, #847) rapporte des comptes plus élevés par type d’attaque (~82 undercut / ~50 undermine / ~5 rebut) parce qu’il unionne, en mode fail-loud, deux sources d’attaque que le grep brut ne sépare pas : les DirectRef (référence directe à un scheme conflictuel) et les ExceptionRef (attaque référencée via une exception de scheme Walton). Ces attaques indirectes — portées par les schemes d’argumentation (§4) et la couche conflict (§8), pas par un aifAttackedNode isolé — gonflent l’undercut surtout, car la plupart des sophismes entrent dans un scheme Walton avant d’être contestés.
Lecture : 87 (grep OWL) vs ~82 (audit 3-axes) pour l’undercut — depuis la resynchronisation #13554, le grep dépasse l’audit, car les deux bornes datent d’états distincts de l’ontologie : l’audit 3-axes a été mesuré sur une révision antérieure du dépôt Argumentum, le grep porte l’OWL présent. Le grep donne le squelette explicite de l’OWL actuel ; l’audit reconstituait le graphe complet d’un état antérieur. Les deux comptes ne se contredisent pas : ils mesurent des états différents.
Honnêteté (règle C.4) : les valeurs 82/50/~5 sont des measures rapportées par l’audit Argumentum (PR #847, layer c #857 closed non-merged, « MEASURE not verdict »). Elles ne sont pas reproductibles depuis le seul OWL CoursIA (le générateur #763 ne sérialise que les aifAttackedNode explicites) ; la doc méthodologique docs/taxonomy/aif-structural-audit-method.md vit dans le dépôt Argumentum, pas dans CoursIA. Verdict : CAUSE_DOCUMENTED_ONLY — la décomposition 87/53/5 reste l’unique mesure vérifiable ici.
# --- §7 illustre : 2-3 exemples incarnes par type d'attaque AIF ---from collections import Counter, defaultdictdef _loc(x):return x.rstrip("#").split("#")[-1].split("/")[-1]label_by = defaultdict(dict)for _iri, _lang, _lab in pref_matches:if _lab: label_by[_loc(_iri)][(_lang or"").lower()] = _labdef fr(n): return label_by.get(n, {}).get("fr", n)def en(n): return label_by.get(n, {}).get("en", n)literals = defaultdict(dict)for _p, _s, _v in AA_LIT: literals[_loc(_s)][_loc(_p)] = _vCATS = [("RA-node", "undercut", "attaque l'INFERENCE (le lien premisse -> conclusion)"), ("I-node", "undermine", "attaque la PREMISSE (un fait ou un témoignage)"), ("CA-node", "rebut", "attaque la CONCLUSION (affirme l'opposé)")]for node, sense, gloss in CATS: sources = Counter(_loc(s) for p, s, o in AA_RESif _loc(p) =="aifAttackedNode"and _loc(o) == node)print("="*78)print(f"{node} — {sense} ({gloss}) : {sum(sources.values())} sophismes attaquants")print("="*78)for name, cnt in sources.most_common(3): d = literals.get(name, {}).get("definition", "(absente)")print(f" - {fr(name)} / {en(name)} [{name}, {cnt} attaque(s)]")print(f" {d[:150]}")print()
==============================================================================
RA-node — undercut (attaque l'INFERENCE (le lien premisse -> conclusion)) : 87 sophismes attaquants
==============================================================================
- Preuve anecdotique / Anecdotal evidence [anecdotalEvidence, 2 attaque(s)]
Basing an argument solely on a singular incident, which is insufficient to establish a general rule.
- Pente glissante / Slippery slope [slipperySlope, 2 attaque(s)]
Rejecting a proposal by predicting an unlikely sequence of negative events.
- Argument bâclé / Sloppy argument [sloppyArgument, 1 attaque(s)]
Basing an argument on impressions or anecdotes, without solid evidence.
==============================================================================
I-node — undermine (attaque la PREMISSE (un fait ou un témoignage)) : 53 sophismes attaquants
==============================================================================
- Insuffisance / Insufficiency [insufficiency, 1 attaque(s)]
The arguments used fail to provide adequate support for the conclusion.
- Argument vide / Hollow argument [hollowArgument, 1 attaque(s)]
Using assertions that contribute nothing to building a convincing argument.
- Sauvetage ad hoc / Ad hoc rescue [adHocRescue, 1 attaque(s)]
Improvising unfounded justifications in the face of valid objections in order to escape criticism.
==============================================================================
CA-node — rebut (attaque la CONCLUSION (affirme l'opposé)) : 5 sophismes attaquants
==============================================================================
- Obstruction / Obstruction [obstruction, 1 attaque(s)]
Through a rhetorical or other device, you make sure that the discussion does not proceed as planned.
- Refus du débat / Refusing debate [refusingDebate, 1 attaque(s)]
Evading debate by means of preventing a rational argumentative discussion.
- Relativisme abusif / Abusive relativism [abusiveRelativism, 1 attaque(s)]
Claiming that truth is subjective and specific to each individual.
Lecture — la distribution 60/37/3 % ne parle plus seulement en pourcentages : l’undercut (RA-node, attaque de l’inférence) est incarné par la preuve anecdotique, la pente glissante ou la généralisation hâtive — des sophismes qui discrédent le raisonnement lui-même ; l’undermine (I-node) par des attaques de premises ; le rebut (CA-node) reste rare (5 occurrences). Chaque exemple est labellisé FR/EN et défini depuis l’OWL (predicat definition) : le lecteur peut remonter à la fiche complète dans Argumentum.
8. Scheme vs conflict : l’attaque AIF est-elle un scheme ou un conflit ?
Le §7 a montré que les 145 aifAttackedNode se répartissent en trois cibles de scheme (RA-node / I-node / CA-node = undercut / undermine / rebut). Mais en AIF canonique, le scheme (le type du nœud attaqué) et le conflit (la relation d’attaque elle-même) sont deux couches distinctes : un conflit relie un nœud attaquant à un nœud attaqué via un nœud d’application (CA-node). L’OWL Argumentum possède-t-il une couche de conflit séparée, ou tout passe-t-il par le prédicat de scheme ?
Pour le savoir, on croise dans AA_RES les prédicats candidats : aifAttackedNode (le scheme, mesuré au §7), hasConflictedElement (le prédicat de conflit dédié, déclaré dans le schéma) et aifAttackingNode (la source de l’attaque).
# --- Scheme vs conflict : aifAttackedNode (scheme) vs hasConflictedElement (conflit dédié) ---from collections import Counterscheme = Counter() # aifAttackedNode = la cible de scheme (RA/I/CA) -- vu au §7conflict = Counter() # hasConflictedElement = le prédicat de conflit DEDIÉhas_attacking =0# aifAttackingNode = la SOURCE de l'attaque (l'attaquant)for pred_iri, _subj, obj_iri in AA_RES: pred = pred_iri.rstrip("#").split("#")[-1].split("/")[-1]if pred =="aifAttackedNode": scheme[obj_iri.rstrip("#").split("#")[-1].split("/")[-1]] +=1elif pred =="hasConflictedElement": conflict[obj_iri.rstrip("#").split("#")[-1].split("/")[-1]] +=1elif pred =="aifAttackingNode": has_attacking +=1n_scheme =sum(scheme.values())n_conflict =sum(conflict.values())print(f"SCHEME aifAttackedNode (cible RA/I/CA) : {n_scheme} triplets instanciés")for k in ("RA-node", "I-node", "CA-node"):print(f" {k:<8} : {scheme[k]:>3d}")print(f"CONFLICT hasConflictedElement (dédié) : {n_conflict} triplet instancié")print(f"SOURCE aifAttackingNode (l'attaquant) : {has_attacking} triplet instancié")print()print("=> La couche de conflit dédiée (hasConflictedElement) est déclarée")print(" dans le schéma de l'OWL mais n'a AUCUN triplet instancié.")
SCHEME aifAttackedNode (cible RA/I/CA) : 145 triplets instanciés
RA-node : 87
I-node : 53
CA-node : 5
CONFLICT hasConflictedElement (dédié) : 0 triplet instancié
SOURCE aifAttackingNode (l'attaquant) : 0 triplet instancié
=> La couche de conflit dédiée (hasConflictedElement) est déclarée
dans le schéma de l'OWL mais n'a AUCUN triplet instancié.
Lecture — scheme et conflit ne sont pas séparés dans l’OWL Argumentum. Le prédicat de scheme aifAttackedNode porte l’intégralité de la structure d’attaque (145 triplets, §7), tandis que le prédicat de conflit dédiéhasConflictedElement — bien que déclaré dans le schéma (39 occurrences à l’épingle courante : sa déclaration ObjectProperty plus ses axiomes subPropertyOf / domain / range) — n’a aucun triplet instancié (0 sur les données). Symétriquement, il n’existe pas de aifAttackingNode : la source de l’attaque n’est pas modélisée.
L’OWL ne distingue donc pas, en pratique, le scheme (le type du nœud attaqué) du conflit (la relation d’attaque) : le scheme est le conflit. Chaque sophisme est relié à un nœud cible typé (RA/I/CA), et c’est ce typage qui encode toute la sémantique d’attaque — il n’y a pas de graphe de conflit séparé. C’est un choix de modélisation délibéré : Argumentum capture la sémantique de l’attaque (undercut / undermine / rebut via le type de la cible) plutôt que sa topologie (qui attaque qui). Un moteur d’argumentation aval (ASPIC+, Dung) qui aurait besoin du graphe d’attaque complet devrait donc le reconstruire à partir des aifAttackedNode, la couche de conflit explicite étant absente.
Note méthodologique (scope #802 / #4960). La convention de référence « scheme-vs-conflict » est documentée dans 141-aif-stage3-adjudication.md (répertoire Argumentum, submodule non initialisé dans CoursIA). Cet audit se fonde exclusivement sur l’OWL réellement committé (ontologies/argumentum_fallacies.owl) — jamais sur une révision externe du submodule ni sur des nombres rapportés — conformément à la discipline du §7 (#8319) et au périmètre « patrimoine technique stable » (#802 : sémantique d’attaque AIF, méthode d’audit, structure de taxonomie).
# --- §8 demontre : le graphe d'attaque rend le verdict evident ---from collections import defaultdictimport matplotlibmatplotlib.use("Agg")import matplotlib.pyplot as pltfrom pathlib import Pathfrom IPython.display import SVG, displaydef _loc(x):return x.rstrip("#").split("#")[-1].split("/")[-1]label_by = defaultdict(dict)for _iri, _lang, _lab in pref_matches:if _lab: label_by[_loc(_iri)][(_lang or"").lower()] = _labdef fr(n): return label_by.get(n, {}).get("fr", n)# Trois attaquants REELS du RA-node, choisis PAR ANNOTATION et tries par libelle FR.# Pas d'IRI en dur : au pin courant, l'IRI hastyGeneralization designe un autre# noeud (sans attaque) -- l'ancien porteur de l'attaque est devenu sloppyArgument# (renommages amont, cf. #17669 / ArgumentumGames/Argumentum#1525).attack_edges = [(_loc(s), _loc(o)) for p, s, o in AA_RES if _loc(p) =="aifAttackedNode"]ra_attackers =sorted({s for s, o in attack_edges if o =="RA-node"}, key=lambda name: fr(name).lower())DEMO = ra_attackers[:3]assert DEMO andall(any(s == d and o =="RA-node"for s, o in attack_edges) for d in DEMO), \"les exemples DEMO doivent etre des attaquants RA-node mesures dans l'OWL"demo_edges = [(s, o) for s, o in attack_edges if s in DEMO]targets =sorted({o for _, o in demo_edges})attacking = [(_loc(s), _loc(o)) for p, s, o in AA_RES if _loc(p) =="aifAttackingNode"]fig, ax = plt.subplots(figsize=(12, 7.5))for i, s inenumerate(DEMO): y =2.2- i *1.1 ax.annotate(f"{fr(s)}\n({s})", (-3.4, y), ha="center", va="center", fontsize=10, color="#263238", bbox=dict(boxstyle="round,pad=0.35", fc="#cfd8dc", ec="#90a4ae"), zorder=3)for j, t inenumerate(targets): ty =1.6- j *1.3if (s, t) in demo_edges: ax.plot([-2.5, -0.4], [y, ty], color="#6a1b9a", lw=2.0, zorder=2)for j, t inenumerate(targets): ty =1.6- j *1.3 n_att =sum(1for _, o in attack_edges if o == t) ax.annotate(f"{t}\n{n_att} attaques", (0.6, ty), ha="center", va="center", fontsize=10, color="white", bbox=dict(boxstyle="round,pad=0.35", fc="#6a1b9a", ec="none"), zorder=3)# La couche conflit dediee : declaree, JAMAIS instanciee -> noeud fantome pointe.ax.annotate("hasConflictedElement\n(conflit dedie : 0 triplet instancie)", (4.0, 1.6), ha="center", va="center", fontsize=9.5, color="#9e9e9e", style="italic", bbox=dict(boxstyle="round,pad=0.35", fc="white", ec="#9e9e9e", ls="--"), zorder=3)ax.annotate("(aifAttackedNode porte deja\nle scheme ET le conflit)", (4.0, 0.1), ha="center", va="center", fontsize=9, color="#6a1b9a")ax.set_title("Scheme vs conflit : aifAttackedNode tire deja tout seul", fontsize=12, fontweight="bold")ax.set_xlim(-5.2, 6.6); ax.set_ylim(-1.2, 3.1); ax.axis("off")plt.tight_layout()OUT_DIR = Path("out"); OUT_DIR.mkdir(exist_ok=True)SCHEME_SVG = OUT_DIR /"argumentum_scheme_vs_conflict.svg"fig.savefig(str(SCHEME_SVG), format="svg", bbox_inches="tight")plt.close(fig)plt.close("all")matplotlib.use("module://ipykernel.pylab.backend_inline")print(f"SVG ecrit : {SCHEME_SVG.name} ({SCHEME_SVG.stat().st_size:,} octets)")print(f"aifAttackingNode instancie : {len(attacking)} triplet(s) -> {attacking[:2]}")print("=> Verdict graphique : les sophismes attaquent via aifAttackedNode (violet) ;")print(" le predicat de conflit dedie hasConflictedElement n'a AUCUN triplet :")print(" le scheme Walton EST deja la couche de conflit.")display(SVG(filename=str(SCHEME_SVG)))
SVG ecrit : argumentum_scheme_vs_conflict.svg (58,846 octets)
aifAttackingNode instancie : 0 triplet(s) -> []
=> Verdict graphique : les sophismes attaquent via aifAttackedNode (violet) ;
le predicat de conflit dedie hasConflictedElement n'a AUCUN triplet :
le scheme Walton EST deja la couche de conflit.
Lecture — le verdict « le scheme est le conflit » devient visible : les arêtes violettes partent des sophismes vers les cibles RA/I/CA, tandis que le nœud hasConflictedElement reste un fantôme pointillé — déclaré dans le schéma OWL, jamais instancié. La couche de conflit dédiée n’existe que sur le papier du schéma ; en pratique, aifAttackedNode (couche scheme) porte déjà tout le travail.
9. Exercices
Quatre exercices formels (règle #2161 : >= 3 exos par notebook pedagogique). Chaque exercice est un stubprint("Exercice a completer") que l’etudiant complete.
Exercice 1 – Distribution des concepts par prefixe de scheme
A partir de arg_classes (liste d’IRI de owl:Class Argumentum) et de taxonomy (dict localname -> labels), calculer combien de concepts sont des classes materialisees de scheme (*_Inference_* / *_Conflict) versus des sophismes simples. Renvoyer un dict {catégorie: compte} et l’afficher trie.
# Exercice 1 a completer# TODO etudiant : a partir de `arg_classes` (IRI de owl:Class) et de `taxonomy`# (dict localname -> labels), separer les classes materialisees de scheme# (`_Inference` / `_Conflict`) des sophismes simples. Renvoyer un dict {cat: n}.print("Exercice a completer")
Exercice a completer
Exercice 2 – Visualisation matplotlib du sous-graphe Equivoque
Reprendre G_equiv de la section 6 et le visualiser avec matplotlib + networkx.spring_layout (seed=42). Colorer les noeuds par degré (entrant + sortant), afficher les labels des proprietes sur les aretes.
Indice : construire un nx.DiGraph, ajouter les aretes depuis G_equiv.items(). Si G_equiv est vide, verifier que semanticAmbiguity est bien le TARGET (il peut etre absent si la taxonomie change).
# Exercice 2 a completer# TODO etudiant : reprendre G_equiv de la section 6, construire un nx.DiGraph,# spring_layout (seed=42), couleur des noeuds par degre, edge_labels affiches.# Si matplotlib n'est pas disponible, fallback ASCII (cf. §6).print("Exercice a completer")
Exercice a completer
Exercice 3 – Detection des sophismes “Ambiguite” et hiérarchie
Implementer une fonction trouver_ambiguite(taxonomy) qui retourne tous les concepts dont le prefLabel fr/en mentionne “ambig”, “equivoc”, “quivoqu”. Renvoyer un dict {local: [labels_matches]}. Faux positifs a eviter : “ambiguite du contexte” hors sophisme. Bonus : croiser avec AA_RES pour afficher, pour chaque match, son parent broader.
# Exercice 3 a completer# TODO etudiant : implementer trouver_ambiguite(taxonomy) qui retourne# un dict {local: [labels_matches]} sur les prefLabel fr/en. Bonus : croiser# avec AA_RES (predicat 'broader') pour afficher le parent de chaque match.print("Exercice a completer")
Exercice a completer
Exercice 4 – Undercut vs undermine : quels sophismes attaquent la prémisse ?
À partir de AA_RES et de la mesure du §7, lister les sophismes (par localnameetprefLabel FR) dont l’attaque AIF cible un I-node (undermine) plutôt qu’un RA-node (undercut). Observer leur thème : l’undermine porte-t-il préférentiellement sur des sophismes de preuve / témoignage (où c’est l’information qui est attaquée) ?
Indice : croiser AA_RES avec taxonomy (dict localname → prefLabel) pour récupérer les libellés. Bonus : afficher aussi le parent broader de chaque match (predicat broader dans AA_RES).
# Exercice 4 a completer# TODO etudiant : a partir de AA_RES, filtrer les aifAttackedNode -> I-node (undermine).# Croiser avec `taxonomy` (localname -> prefLabel) pour afficher le label FR de chaque match.# Bonus : afficher aussi le parent `broader` de chaque match (predicat 'broader' dans AA_RES).print("Exercice a completer")
Exercice a completer
Exercice 5 – Lire un lien transverse crossLink sur la carte
Depuis la dé-curation (#1286), mirrors ne porte plus qu’une paire (§5.1) : l’exercice se joue désormais sur le verbe dominant de la couche transverse, leverages (327 arêtes). Choisir un émetteur leverages (par exemple dans le top : emotiveConjugation, damningWithFaintPraise, gishGallop, pseudoscience) et décrire en 3 phrases ce que relie l’arête : qui sont les deux extrémités, qu’est-ce qu’elles partagent, et où la hiérarchie SKOS (broader) les sépare. On pourra s’inspirer de la lecture de la paire survivante §5.1.
# Exercice 5 a completer# Choix de l'emetteur : remplacer par un emetteur leverages reel (cf. top cite en# exercice 5 : emotiveConjugation, damningWithFaintPraise, gishGallop, pseudoscience).EMETTEUR ="emotiveConjugation"# TODO etudiant : choisir un autre emetteur leverages# 1) Recuperer les aretes leverages emises par EMETTEUR (meme methode que l'audit mirrors)# 2) Afficher les prefLabel FR/EN et les parents broader des deux extremites de chaque arete# 3) Rediger les 3 phrases (qui, partage, ou la hierarchie les separe)print("Exercice a completer")
Exercice a completer
10. Ponts avec la serie
Direction
Lien
Relation
<-> ArgumentProfile
Argumentation-08e-Argument-Profile-Python.ipynb
Vue d’ensemble de la serie, introduction aux schemes
<-> CrossLinks (PR-B)
Argumentation-Onto-02-CrossLinks-CSV-Python.ipynb
Substance CSV canonique — reconciliee avec cet OWL par #763
-> SemanticKernel
SemanticKernel/*
Exploitation des schemes pour l’analyse d’arguments
Honnetete methodologique
Parseur regex, pas un moteur RDF : rdflib echoue au parse et owlready2 expose 0 classe via l’API Python. Le parseur regex est robuste pour extraire la structure mais ne fait pas de raisonnement (pas d’inference de subsomption, pas de fermeture transitive). Pour un raisonnement OWL complet, il faudrait corriger l’idiome de serialisation en amont (générateur Argumentum).
L’OWL utilise desormais l’AIF (inversion de la version précédente de ce notebook) : 145 concepts portent aifAttackedNode → RA/I/CA-node (undercut→RA, undermine→I, rebut→CA, ratifie #707§4). La note historique « l’OWL n’utilise PAS l’AIF » est obsolete.
Les crossLink sont dans l’OWL (inversion) : 735 AnnotationAssertion à l’épingle courante (leverages/isRelatedTo/inverts/… ; 1 977 à l’état 30/08). La note historique « crossLink_* CSV-only, hors-repo » est obsolete — #763 a cable le chemin CSV→OWL.
Ontologie soeur presente : argumentum_virtues.owl est desormais dans ontologies/ (aux cotes de argumentum_fallacies.owl). La note historique « argumentum_virtues.owl absente » est obsolete.
Idiome annotation-space, nuance du 30/08 : toutes les relations concept→concept (hiérarchie SKOS, crossLink, AIF) sont des AnnotationAssertion, consequence du bug SKOSHelper d’OWLSharp. Mais le fichier livré porte par ailleurs 880 ObjectPropertyAssertion à l’épingle courante (2 122 à l’état 30/08), réintroduites par l’amont (#1231) avec la resynchronisation du 30/08 — le « et non des OPA » de la première rédaction est obsolete. C’est un choix de serialisation du générateur, pas une limite de modelisation — les 10 ObjectProperty sont bien declarees.