IIT - Introduction à PyPhi et Integrated Information Theory

Objectifs d’apprentissage

  1. Comprendre les principes de l’Integrated Information Theory (IIT)
  2. Manipuler les objets PyPhi : Network, Subsystem, CES
  3. Calculer et interpréter la valeur Φ (Phi) d’un système
  4. Explorer la structure causale (Cause-Effect Structure)

Prérequis

  • Python 3.10+ (environnement Conda recommandé)
  • Notions de base en théorie de l’information
  • Connaissances en algèbre linéaire (matrices de transition)

Durée estimée : 60 minutes


Sommaire

  1. Introduction à l’IIT
  2. Installation et importation de PyPhi
  3. Création d’un réseau simple (Network)
  4. Définition d’un Subsystem et calcul de \(\Phi\)
  5. Concepts, mécanismes, et structure cause-effet (CES)
  6. Exploration d’exemples avancés (macro, actual causation…)
  7. Ressources complémentaires

## 1. Introduction à l’IIT

Objectif : l’Integrated Information Theory (IIT), introduite par Giulio Tononi et collaborateurs, propose une mesure de la conscience à partir de la quantité d’information intégrée par un système. En pratique :

  • Un système (réseau de neurones, circuit logique, etc.) est représenté par une Transition Probability Matrix (TPM).
  • L’information intégrée est notée \(\Phi\) (Phi). PyPhi calcule \(\Phi\) pour des sous-systèmes et explore la structure causale (Cause-Effect Structure, ou CES).
  • L’objectif pédagogique de ce notebook est de créer un petit réseau, de définir un état, et de calculer \(\Phi\) pour différents sous-ensembles (subsystèmes).

Objectifs du TP

  1. Comprendre la représentation d’un système dynamique discret via une TPM.
  2. Manipuler les objets de base de PyPhi : Network, Subsystem.
  3. Calculer et interpréter la valeur \(\Phi\) (degré d’irréductibilité).
  4. Explorer la structure causale (CES) d’un état donné.

Résumé des concepts clés

Concept Explication simple Analogie
TPM Les règles du jeu. Comment le système évolue. Les lois de la physique.
État La configuration actuelle (0 ou 1) des nœuds. Une photo instantanée du cerveau.
\(\Phi\) (Big Phi) Le niveau d’intégration du système entier. La solidité d’un nœud marin (tient-il tout seul ?).
MIP Le point faible du système (Minimum Information Partition). Le maillon faible d’une chaîne.
CES La forme géométrique de l’information intégrée. La “forme” d’une pensée.

## 2. Installation et importation de PyPhi

Remarque : PyPhi s’installe via pip. Pré-requis : Python 3.10+ (recommandé).

Il est conseillé d’utiliser un environnement Conda dédié :

conda create --name pyphi python=3.13 -y
conda activate pyphi
pip install pyphi

Sélectionner l’environnement nouvellement créé comme noyau.

Cadre. Ce notebook établit le calcul opérationnel de \(\Phi\) (petite phi) sur un réseau toy avec PyPhi : construction du sous-système, mécanisme d’information minimale irréductible (MIP), puis cause-effect structure (CES). C’est le socle mécanique de toute la série IIT.

Du côté ICT. L’Epic #4588 demande : qu’est-ce qu’une « strate » mesure quand on parle d’information intégrée ? Le \(\Phi\) calculé ici en est l’instrument. Le fil ICT qui prolonge cette mécanique — la dissolution des scalaires — suit ce qui arrive à \(\Phi\), F, K quand on les pousse hors de leur substrat d’origine (ICT-1 → ICT-22).

import warnings
# pyemd (importe par pyphi) emet un avertissement de deprecation pkg_resources qui fuite le chemin site-packages.
warnings.filterwarnings("ignore", message=".*pkg_resources is deprecated.*", category=UserWarning)

# Installation defensive : pyphi est deja present dans le kernel dedie (env conda pyphi).
# On ne lance pip que s'il manque, pour eviter la sortie bruyante (chemins site-packages).
import importlib.util, subprocess, sys
if importlib.util.find_spec("pyphi") is None:
    subprocess.run([sys.executable, "-m", "pip", "install", "-q", "pyphi"], check=True)
    print("pyphi installe.")
else:
    import pyphi
    print(f"pyphi deja installe ({pyphi.__version__}).")

Welcome to PyPhi!

If you use PyPhi in your research, please cite the paper:

  Mayner WGP, Marshall W, Albantakis L, Findlay G, Marchman R, Tononi G.
  (2018). PyPhi: A toolbox for integrated information theory.
  PLOS Computational Biology 14(7): e1006343.
  https://doi.org/10.1371/journal.pcbi.1006343

Documentation is available online (or with the built-in `help()` function):
  https://pyphi.readthedocs.io

To report issues, please use the issue tracker on the GitHub repository:
  https://github.com/wmayner/pyphi

For general discussion, you are welcome to join the pyphi-users group:
  https://groups.google.com/forum/#!forum/pyphi-users

To suppress this message, either:
  - Set `WELCOME_OFF: true` in your `pyphi_config.yml` file, or
  - Set the environment variable PYPHI_WELCOME_OFF to any value in your shell:
        export PYPHI_WELCOME_OFF='yes'

pyphi deja installe (1.2.0).

Une fois PyPhi installe, importons la librairie et verifions la version disponible.

import warnings
# pyemd (importe par pyphi) emet un avertissement de deprecation pkg_resources qui fuite le chemin site-packages.
warnings.filterwarnings("ignore", message=".*pkg_resources is deprecated.*", category=UserWarning)

import pyphi

print("PyPhi version:", pyphi.__version__)
PyPhi version: 1.2.0

## 3. Création d’un réseau simple (Network)

3.1. Structure d’un réseau

Un Network dans PyPhi est défini par : - Une TPM (ExplicitTPM) en forme de matrice multidimensionnelle ou un array 2D. - Une connectivity matrix (CM), indiquant comment les nœuds sont connectés.

3.2. Exemple : un circuit logique

Nous allons créer un réseau (circuit) minimaliste à 3 nœuds (A, B, C) :

  • A : passerelle logique, reçoit des entrées de B et C
  • B : un XOR des entrées de A et C
  • C : identique à B ou un AND/OR, selon l’exemple souhaité
import numpy as np
import pyphi

# Exemple d'un petit réseau XOR
# 3 noeuds : A, B, C
# On utilise un exemple déjà inclus dans pyphi.examples

network = pyphi.examples.xor_network()

print("Nodes:", network.node_labels)
print("TPM shape:", network.tpm.shape)
print("Connectivity matrix:\n", network.cm)
Nodes: NodeLabels(('A', 'B', 'C'))
TPM shape: (2, 2, 2, 3)
Connectivity matrix:
 [[0 1 1]
 [1 0 1]
 [1 1 0]]

Comment ça marche ?
- pyphi.examples.xor_network() renvoie un réseau (Network) de taille 3, où chaque nœud est un XOR à deux entrées (sans auto-connexion).
- La TPM est stockée dans network.tpm, et chaque dimension correspond à un état binaire (0/1). La TPM est un tableau multidimensionnel de taille (2, 2, 2, 3), reflétant les états binaires pour 3 nœuds. La matrice de connectivité montre les liens (XOR signifie que chaque nœud dépend des deux autres sans boucles sur soi-même).


3.3. Inspection de la TPM

Pour mieux comprendre le fonctionnement du réseau, vous pouvez examiner directement les valeurs de la matrice de transition (TPM). Celle-ci peut être soit en state-by-state (SBS) ou state-by-node (SBN) selon votre cas. PyPhi propose des utilitaires de conversion.

import pyphi.convert

sbn_tpm = network.tpm
print("TPM (state-by-node) shape:", sbn_tpm.shape)

# Pour visualiser un 'slice' ou un 'flatten' :
print("Premier 'slice' de la TPM (correspondant à un sous-état) :\n", sbn_tpm[0])

# Passer au format state-by-state pour mieux le parcourir
sbs_tpm = pyphi.convert.state_by_node2state_by_state(sbn_tpm)
print("\nTPM (state-by-state) shape:", sbs_tpm.shape)
print("Quelques lignes du TPM:\n", sbs_tpm[:4])
TPM (state-by-node) shape: (2, 2, 2, 3)
Premier 'slice' de la TPM (correspondant à un sous-état) :
 [[[0. 0. 0.]
  [1. 1. 0.]]

 [[1. 0. 1.]
  [0. 1. 1.]]]

TPM (state-by-state) shape: (8, 8)
Quelques lignes du TPM:
 [[1. 0. 0. 0. 0. 0. 0. 0.]
 [0. 0. 0. 0. 0. 0. 1. 0.]
 [0. 0. 0. 0. 0. 1. 0. 0.]
 [0. 0. 0. 1. 0. 0. 0. 0.]]

## 4. Définition d’un Subsystem et calcul de \(\Phi\)

4.1. Sous-système (Subsystem)

Un Subsystem se définit en spécifiant : 1. Un Network ; 2. Un état du réseau (ex. (0, 0, 0) pour A=0, B=0, C=0) ; 3. Les indices de nœuds internes qu’on considère comme faisant partie du sous-système.

Ensuite, PyPhi peut calculer la valeur de \(\Phi\) pour ce sous-système.

Le Calcul de \(\Phi\) : Définition Intuitive

\(\Phi\) mesure à quel point le système est “plus que la somme de ses parties”.

Le calcul : PyPhi essaie de couper le système en deux (une “bipartition”) de toutes les manières possibles (A vs BC, B vs AC, etc.). - La coupe qui fait perdre le moins d’information est appelée la Minimum Information Partition (MIP). - \(\Phi\) est la quantité d’information perdue au niveau de cette coupe la plus faible. - Si \(\Phi > 0\), le système est irréductible (conscient selon l’IIT).

# État de départ
state = (0, 0, 0)

# Créons un Subsystem pour l’ensemble des nœuds (0, 1, 2)
subsystem = pyphi.Subsystem(network, state, (0, 1, 2))

# Calcul de Phi
phi_value = pyphi.compute.phi(subsystem)

print("Φ pour le sous-système (0,1,2) à l’état (0,0,0) :", phi_value)
Φ pour le sous-système (0,1,2) à l’état (0,0,0) : 1.874999

4.2. States inaccessibles et validation (StateUnreachableError)

C’est une erreur fréquente rencontrée dans PyPhi.

Le problème : Certains états du réseau peuvent être impossibles à atteindre selon la dynamique du système (la TPM). Si on force le système dans cet état “impossible”, l’IIT considère que le passé est indéfini.

Solutions possibles : 1. Choisir un état qui est atteignable (un état stable ou faisant partie d’un cycle). 2. Ou désactiver la validation (pour l’exercice) : pyphi.config.VALIDATE_SUBSYSTEM_STATES = False.

PyPhi, par défaut, peut émettre des erreurs (StateUnreachableError) si l’état que vous définissez n’est pas accessible depuis la TPM. Ci-dessous, on montre comment désactiver temporairement la validation pour s’assurer que PyPhi ne bloque pas le calcul.

# Configurer PyPhi pour ignorer les inaccessibilités d’états
pyphi.config.VALIDATE_SUBSYSTEM_STATES = False

# Réessayer un état arbitraire
test_state = (1,0,0)
subsys_test = pyphi.Subsystem(network, test_state, (0,1,2))
try:
    val_test = pyphi.compute.phi(subsys_test)
    print(f"État {test_state} -> Φ = {val_test:.5f}")
except pyphi.exceptions.StateUnreachableError:
    print("État inaccessible, PyPhi a lancé une exception !")
État inaccessible, PyPhi a lancé une exception !

On peut également calculer \(\Phi\) pour d’autres états accessibles:

# Exemple d'états stables pour éviter StateUnreachableError
for s in [(0,0,0), (0,1,1), (1,1,0)]:
    subsys = pyphi.Subsystem(network, s, (0,1,2))
    val = pyphi.compute.phi(subsys)
    print(f"État {s} -> Φ = {val:.5f}")
État (0, 0, 0) -> Φ = 1.87500
État (0, 1, 1) -> Φ = 1.87500
État (1, 1, 0) -> Φ = 1.87500

Interprétation : pourquoi ces trois états donnent-ils le même \(\Phi\) ?

Vous venez de calculer \(\Phi\) pour trois états distincts — (0,0,0), (0,1,1) et (1,1,0) — et les trois donnent exactement la même valeur \(\Phi = 1.87500\). Ce n’est ni une coïncidence numérique ni un bug de PyPhi : c’est une propriété profonde de la mesure.

\(\Phi\) ne dépend pas de l’état pris isolément, mais de la structure cause-effet (CES) du sous-système dans cet état. Sur le réseau XOR canonique (déterministe), les trois états testés appartiennent à un même cycle de la dynamique, et ils engendrent des structures cause-effet identiques — mêmes mécanismes, mêmes purviews, mêmes petits \(\varphi\) par distinction. Comme \(\Phi\) est le minimum informationnel sur l’ensemble de cette structure (la coupe la plus faible du graphe cause-effet), et que la structure coïncide d’un état à l’autre, \(\Phi\) coïncide aussi.

La leçon conceptuelle est essentielle : \(\Phi\) quantifie une propriété intrinsèque au sous-système et à sa connectivité, évaluée relativement à un état mais non réductible à lui seul. Deux états différents peuvent porter la même « expérience » informationnelle dès lors que leur structure cause-effet coïncide. C’est précisément ce qui distingue \(\Phi\) d’une simple entropie de l’état ou d’un compte de corrélations : il capte l’organisation causale, pas la configuration instantanée.

À retenir : sur un réseau bruité ou asymétrique, la structure cause-effet varierait d’un état à l’autre et \(\Phi\) en différerait. L’invariance observée ici est la signature d’un réseau déterministe symétrique — elle disparaîtrait dès qu’on introduit du hasard ou une asymétrie dans la matrice de transition (TPM). Ce point sera réexploré au paragraphe suivant sur les sous-systèmes partiels.

Démonstration : une asymétrie de la TPM brise l’invariance de \(\Phi\)

L’interprétation précédente énonçait un fait fort : sur le réseau XOR symétrique, \(\Phi = 1{,}875\) est invariant sur les états atteignables, et cette invariance est la signature d’une dynamique déterministe symétrique. Mais elle ajoutait une prédiction — « l’invariance disparaîtrait dès qu’on introduit une asymétrie dans la TPM » — qui, jusqu’ici, restait non démontrée.

Vérifions-le empiriquement. Nous construisons un réseau quasi identique au XOR canonique, sauf sur un seul noeud : nous conservons node0 = XOR(b, c) et node1 = XOR(a, c), mais nous remplaçons node2 = XOR(a, b) par la fonction booléenne asymétrique node2 = OR(a, AND(b, c)). La connectivité reste complète. Cette unique perturbation suffit-elle à faire varier \(\Phi\) d’un état à l’autre ? PyPhi va trancher.

# Démonstration : une asymétrie de la TPM brise l'invariance de Phi
# Le réseau XOR canonique (cellule précédente) est parfaitement symétrique :
# chaque noeud calcule le XOR des deux autres, donc la structure cause-effet
# coïncide sur les 4 états atteignables -> Phi = 1.875 invariant (cellule 14).
# Perturbons MINIMALEMENT la dynamique : gardons node0 = XOR(b,c) et
# node1 = XOR(a,c), mais modifions node2 en OR(a, AND(b,c)) -- une fonction
# booléenne asymétrique. Cette unique asymétrie suffit à faire varier Phi.

# Matrice de transition (TPM) du réseau asymétrique -- format état-par-noeud.
tpm_asym = np.zeros((2, 2, 2, 3))
for a in (0, 1):
    for b in (0, 1):
        for c in (0, 1):
            n0 = (b + c) % 2            # node0 = XOR(b, c)    (canonique)
            n1 = (a + c) % 2            # node1 = XOR(a, c)    (canonique)
            n2 = int(a or (b and c))    # node2 = OR(a, AND(b, c))  (PERTURBÉ)
            tpm_asym[a, b, c] = [n0, n1, n2]

# Connectivité complète (chaque noeud lit les deux autres), comme le canonique.
cm = np.ones((3, 3), dtype=int)
network_asym = pyphi.Network(tpm_asym, cm)

# Rappel -- réseau SYMÉTRIQUE canonique : Phi invariant
phi_sym = pyphi.compute.phi(pyphi.Subsystem(network, (0, 0, 0), (0, 1, 2)))
print("=" * 62)
print("RÉSEAU SYMÉTRIQUE canonique (XOR) : Phi invariant")
print("=" * 62)
print(f"  État (0,0,0) -> Phi = {phi_sym:.4f}  (idem sur (0,1,1), (1,0,1), (1,1,0))")

print("\n" + "=" * 62)
print("RÉSEAU ASYMÉTRIQUE (node2 perturbé) : Phi varie selon l'état")
print("=" * 62)
phis_asym = []
for s in [(a, b, c) for a in (0, 1) for b in (0, 1) for c in (0, 1)]:
    try:
        val = pyphi.compute.phi(pyphi.Subsystem(network_asym, s, (0, 1, 2)))
        phis_asym.append((s, val))
        print(f"  État {s} -> Phi = {val:.4f}")
    except pyphi.exceptions.StateUnreachableError:
        print(f"  État {s} -> inaccessible (hors dynamique)")

vals = sorted({round(v, 4) for _, v in phis_asym})
print("-" * 62)
print(f"  Valeurs distinctes de Phi : {len(vals)}  ->  {vals}")
print(f"  CONSTAT : l'asymétrie d'un seul noeud suffit à briser l'invariance.")
print(f"  Phi passe de 1.875 (unique, symétrique) à [{min(vals):.4f} .. {max(vals):.4f}]")
print(f"  ({len(vals)} valeurs distinctes sur {len(phis_asym)} états atteignables).")
==============================================================
RÉSEAU SYMÉTRIQUE canonique (XOR) : Phi invariant
==============================================================
  État (0,0,0) -> Phi = 1.8750  (idem sur (0,1,1), (1,0,1), (1,1,0))

==============================================================
RÉSEAU ASYMÉTRIQUE (node2 perturbé) : Phi varie selon l'état
==============================================================
  État (0, 0, 0) -> Phi = 1.5781
  État (0, 0, 1) -> Phi = 2.7431
  État (0, 1, 0) -> inaccessible (hors dynamique)
  État (0, 1, 1) -> Phi = 0.8073
  État (1, 0, 0) -> Phi = 1.3021
  État (1, 0, 1) -> Phi = 2.0569
  État (1, 1, 0) -> Phi = 1.0781
  État (1, 1, 1) -> Phi = 1.8669
--------------------------------------------------------------
  Valeurs distinctes de Phi : 7  ->  [0.8073, 1.0781, 1.3021, 1.5781, 1.8669, 2.0569, 2.7431]
  CONSTAT : l'asymétrie d'un seul noeud suffit à briser l'invariance.
  Phi passe de 1.875 (unique, symétrique) à [0.8073 .. 2.7431]
  (7 valeurs distinctes sur 7 états atteignables).

Interprétation : la symétrie, condition de l’invariance de \(\Phi\)

Le verdict de PyPhi est net et confirme la prédiction :

Réseau États atteignables \(\Phi\) Invariance
XOR symétrique canonique 4 (000, 011, 101, 110) \(1{,}875\) partout Oui (1 valeur)
XOR asymétrique (node2 perturbé) 7 de \(0{,}81\) à \(2{,}74\) Non (7 valeurs distinctes)

Pourquoi l’invariance cède-t-elle ? Sur le réseau symétrique, les quatre états atteignables sont reliés par les permutations de noeuds : la structure cause-effet (CES) qu’ils engendrent est identique, donc le \(\Phi\) (information intégrée minimale sur cette structure) coïncide. Dès qu’on asymétrise un seul noeud, cette équivalence par permutation disparaît : chaque état engendre désormais une CES spécifique, et \(\Phi\) devient dépendant de l’état.

La leçon : \(\Phi\) n’est pas une constante du réseau, c’est une quantité évaluée relativement à un état. Le cas symétrique est un cas particulier remarquable où plusieurs états partagent la même CES — d’où l’invariance observée. Ce partage est fragile : il suffit d’une asymétrie locale pour le détruire et révéler la sensibilité de \(\Phi\) à la configuration exacte du sous-système. C’est précisément cette sensibilité qui fait de \(\Phi\) une mesure riche de l’organisation causale d’un mécanisme dans un état donné, et non un simple invariant topologique.

À retenir : l’invariance de \(\Phi\) sur le XOR canonique n’est pas une propriété générale de la mesure — c’est un effet de la symétrie parfaite du réseau. La briser, même partiellement, suffit à restaurer la dépendance de \(\Phi\) vis-à-vis de l’état.

Le complexe majeur : la recherche de frontière intégrée

Jusqu’ici, nous avons choisi la frontière : chaque Subsystem(network, state, nodes) fixe des bords à la main. PyPhi embarque aussi la recherche de la frontière qui maximise \(\Phi\) — le complexe majeur, mise en œuvre du postulat d’exclusion d’IIT 3.0 (parmi les candidats, seul le maximiseur compte) :

pyphi.compute.major_complex(network, state)   # le sous-système de big-Phi maximal
pyphi.compute.complexes(network, state)       # tous les sous-systèmes de big-Phi > 0

Le problème « qui décide des bords ? » reçoit ici sa réponse moteur : la bibliothèque énumère les candidats et tranche. IIT-4 consacre un notebook entier à ce choix de frontière — y compris le cas où le complexe majeur n’est pas le réseau entier.

# --- Le moteur tranche lui-meme : recherche du complexe majeur sur le reseau XOR ---
pyphi.config.PROGRESS_BARS = False   # sorties propres (les cellules precedentes laissent les barres)
complexe_majeur = pyphi.compute.major_complex(network, state)
noeuds_cm = tuple(network.node_labels[i] for i in complexe_majeur.subsystem.node_indices)
print("major_complex(network, (0,0,0)) :")
print("   noeuds retenus :", noeuds_cm, "| big-Phi = %.5f" % complexe_majeur.phi)
print()
print("complexes(network, (0,0,0)) — tous les sous-systemes de big-Phi > 0 :")
for sia in pyphi.compute.complexes(network, state):
    noms_c = tuple(network.node_labels[i] for i in sia.subsystem.node_indices)
    print("   %-10s big-Phi = %.5f" % (str(noms_c), sia.phi))
print()
print("Sur ce reseau XOR, le complexe majeur est le systeme ENTIER (maximiseur unique) ;")
print("les trois paires sont ex aequo a Phi = 1.0 — sous le maximum, pas au sommet.")
major_complex(network, (0,0,0)) :
   noeuds retenus : ('A', 'B', 'C') | big-Phi = 1.87500

complexes(network, (0,0,0)) — tous les sous-systemes de big-Phi > 0 :
   ('A', 'B', 'C') big-Phi = 1.87500
   ('B', 'C') big-Phi = 1.00000
   ('A', 'C') big-Phi = 1.00000
   ('A', 'B') big-Phi = 1.00000

Sur ce reseau XOR, le complexe majeur est le systeme ENTIER (maximiseur unique) ;
les trois paires sont ex aequo a Phi = 1.0 — sous le maximum, pas au sommet.

Exercice 1 : Phi d’un sous-système partiel

Le calcul de Phi ci-dessus utilise les 3 noeuds (0,1,2). Que se passe-t-il si on ne considère qu’un sous-ensemble de noeuds, par exemple (0,1) uniquement ?

Objectif : Créez un sous-système avec uniquement les noeuds (0,1) à l’état (0,0,0) et comparez sa valeur Phi avec celle du système complet.

Questions : - Phi est-il plus grand ou plus petit avec 2 noeuds qu’avec 3 ? - Combien de concepts la CES d’un sous-système partiel contient-elle ?

Indice : pyphi.Subsystem(network, state, (0, 1)) crée le sous-système partiel. Puis pyphi.compute.phi(...) pour le Phi et pyphi.compute.ces(...) pour la CES.

# Exercice 1 : Phi d'un sous-systeme partiel (noeuds 0 et 1 uniquement)
# TODO etudiant : creer un sous-systeme avec (0, 1) a l'etat (0,0,0)
# et calculer phi + nombre de concepts dans la CES
# Indice : pyphi.Subsystem(network, (0,0,0), (0, 1))
# Indice : pyphi.compute.phi(subsystem_2noeuds) pour Phi
# Indice : len(pyphi.compute.ces(subsystem_2noeuds)) pour le nombre de concepts

result = None  # TODO etudiant : remplacer par le calcul Phi partiel vs complet
if result is not None:
    print(f"Resultat: {result}")
else:
    print("Exercice a completer")
Exercice a completer

## 5. Concepts, mécanismes, et structure cause-effet (CES)

PyPhi peut également détailler la structure de cause-effet (CES) :

  • Chaque concept correspond à un mécanisme (ex. nœud simple ou sous-groupe de nœuds) et à son purview (cause et/ou effet).
ces = pyphi.compute.ces(subsystem)
print("Nombre de concepts dans la CES :", len(ces))
print("Liste des mécanismes :", ces.labeled_mechanisms)
Nombre de concepts dans la CES : 3
Liste des mécanismes : (['A', 'B'], ['A', 'C'], ['B', 'C'])

5.1. Analyse d’un concept spécifique

Chaque concept est défini par un mécanisme (un ensemble de nœuds) et un purview (un ensemble de nœuds qui subissent ses effets ou le causent). Explorons le premier concept dans la CES, pour comprendre sa cause / effet et son petit phi (\(\\varphi\)).

# Prenons le premier concept dans la CES
first_concept = ces[0]
print("Mécanisme:", first_concept.mechanism)
print("Cause purview:", first_concept.cause.purview)
print("Cause repertoire:\n", first_concept.cause.repertoire)
print("Effect purview:", first_concept.effect.purview)
print("Effect repertoire:\n", first_concept.effect.repertoire)
print("Petit phi (φ) =", first_concept.phi)
Mécanisme: (0, 1)
Cause purview: (0, 1, 2)
Cause repertoire:
 [[[0.5 0. ]
  [0.  0. ]]

 [[0.  0. ]
  [0.  0.5]]]
Effect purview: (2,)
Effect repertoire:
 [[[1. 0.]]]
Petit phi (φ) = 0.5

Exercice 2 : Comparer les concepts de la CES

Le premier concept analysé (mécanisme {A,B}) a un petit phi de 0.5 et un effect purview sur C uniquement. Les autres concepts de la CES peuvent avoir des structures très différentes.

Objectif : Sélectionnez le concept correspondant au mécanisme {A,C} (le deuxième concept de la CES) et analysez son cause/effect purview et son petit phi.

Indice : Utilisez ces[1] pour accéder au deuxième concept. Comparez les purviews avec le premier concept. Les purviews sont-ils les mêmes ? Pourquoi ?

# Exercice 2 : Analyser le deuxieme concept de la CES (mecanisme {A,C})
# TODO etudiant : acceder a ces[1] et afficher :
#   - le mecanisme
#   - le cause purview et repertoire
#   - l'effect purview et repertoire
#   - le petit phi
# Indice : le pattern est le meme que pour first_concept = ces[0] ci-dessus

result = None  # TODO etudiant : remplacer par l'analyse du deuxieme concept
if result is not None:
    print(f"Resultat: {result}")
else:
    print("Exercice a completer")
Exercice a completer

Interpretation : structure causale du premier concept

Le mécanisme {A, B} (noeuds 0 et 1) produit un concept avec :

Aspect Valeur Signification
Cause purview (A, B, C) Les 3 noeuds contribuent a causer l’etat actuel de {A, B}
Effect purview (C,) Le mécanisme {A, B} n’a d’effet irreductible que sur le noeud C
Petit phi 0.5 L’information integree par ce mécanisme spécifique

Le repertoire de cause montre que l’etat passe le plus probable etait (0,0,0) ou (1,1,1) avec probabilite 0.5 chacun. Le repertoire d’effet indique que C passera a l’etat 0 avec certitude (probabilite 1.0), ce qui est coherent avec le XOR : si A=0 et B=0, alors C = A XOR B = 0.

## 6. Exemple avancé : analyse de causalité et macro-subsystems

6.1 Actual Causation

PyPhi offre pyphi.actual pour analyser la causalité effective (“What caused what?”). On définit un Transition : état avant, état après, et on cherche les liens causaux irréductibles.

Cette extension de la théorie permet de comprendre non plus “combien de conscience” (système) mais “qui a causé quoi” (causalité événementielle). C’est utile pour analyser des séquences temporelles spécifiques.

import pyphi
import pyphi.actual

pyphi.config.VALIDATE_SUBSYSTEM_STATES = False

before_state = (1,1,1)
after_state  = (0,0,0)

transition = pyphi.actual.Transition(
    network,
    before_state,
    after_state,
    cause_indices=(0,1,2),
    effect_indices=(0,1,2)
)

account = pyphi.actual.account(transition)
print("Nombre de causal links :", len(account))
for link in account:
    print(link)
Nombre de causal links : 10
CausalLink
  α = 1.0  [B, C] ◀━━ [A]
CausalLink
  α = 1.0  [A, C] ◀━━ [B]
CausalLink
  α = 1.0  [A, B] ◀━━ [C]
CausalLink
  α = 1.0  [A, B, C] ◀━━ [A, B]
CausalLink
  α = 1.0  [A, B, C] ◀━━ [A, C]
CausalLink
  α = 1.0  [A, B, C] ◀━━ [B, C]
CausalLink
  α = 1.0  [A, B] ━━▶ [C]
CausalLink
  α = 1.0  [A, C] ━━▶ [B]
CausalLink
  α = 1.0  [B, C] ━━▶ [A]
CausalLink
  α = 1.0  [A, B, C] ━━▶ [A, B, C]

Interpretation : liens causaux de la transition (1,1,1) vers (0,0,0)

L’analyse revele 10 liens causaux irreductibles, tous avec une force alpha = 1.0. Cette saturation uniforme n’est PAS le signe d’une “structure riche” : elle reflete au contraire une limite du reseau XOR pour la causalite evenementielle. Le reseau XOR est deterministe (chaque noeud = XOR exact de ses deux voisins), donc toute transition valide est parfaitement determinee — chaque mecanisme cause son purview avec la force maximale alpha = 1.0, sans aucune nuance.

En d’autres termes : sur un reseau deterministe, la causalite evenementielle ne discrimine pas — elle attribue la meme force maximale a tous les liens. Pour voir pyphi.actual exercer sa capacite distinctive (distinguer les causes fortes des causes faibles), il faut un reseau probabiliste, dont les transitions ne sont pas parfaitement determinees. C’est l’objet de la cellule suivante.

6.1bis Causalite evenementielle sur un reseau probabiliste (discrimination)

Pour voir la causalite evenementielle discriminer entre liens forts et faibles, nous construisons un reseau noisy-AND : meme connectivite que le XOR (anneau complet a 3 noeuds A, B, C), mais chaque porte AND est bruitee — la transition n’est plus deterministe. La probabilite qu’un noeud s’allume vaut p si ses deux entrees sont allumees, sinon une petite probabilite de bruit. Avec p = 0.9, certains mecanismes causent leur purview de maniere forte (alpha eleve) et d’autres de maniere faible (alpha faible) : la machinerie pyphi.actual peut alors distinguer les vraies causes des causes marginales, ce qu’elle ne pouvait pas faire sur le reseau XOR sature.

import numpy as np
import pyphi.actual

pyphi.config.VALIDATE_SUBSYSTEM_STATES = False

# Reseau noisy-AND : porte AND bruitee (p=0.9 si les 2 entrees sont ON, sinon bruit).
# Meme connectivite que le XOR (anneau complet), mais TPM NON deterministe.
def noisy_and_tpm(p=0.9):
    tpm = np.zeros((2, 2, 2, 3))
    for a in (0, 1):
        for b in (0, 1):
            for c in (0, 1):
                for ni, inp in enumerate([(b, c), (a, c), (a, b)]):
                    tpm[a, b, c, ni] = p if (inp[0] == 1 and inp[1] == 1) else (1 - p) * 0.5
    return tpm

p = 0.9
tpm_noisy = noisy_and_tpm(p)
cm_noisy = np.array([[0, 1, 1], [1, 0, 1], [1, 1, 0]])
reseau_noisy = pyphi.Network(tpm_noisy, cm=cm_noisy, node_labels=("A", "B", "C"))

# Une transition ou les contributions causales different d'un noeud a l'autre.
avant = (1, 1, 0)
apres = (0, 1, 1)
transition_noisy = pyphi.actual.Transition(
    reseau_noisy, avant, apres, cause_indices=(0, 1, 2), effect_indices=(0, 1, 2)
)
compte_noisy = pyphi.actual.account(transition_noisy)

print(f"Transition {avant} -> {apres} sur le reseau noisy-AND (p={p})")
print(f"Nombre de liens causaux : {len(compte_noisy)}")
alphas = [round(float(lien.alpha), 3) for lien in compte_noisy]
for lien in compte_noisy:
    print(lien)
print()
print(f"Force alpha : min={min(alphas)}, max={max(alphas)}, valeurs distinctes={sorted(set(alphas))}")
Transition (1, 1, 0) -> (0, 1, 1) sur le reseau noisy-AND (p=0.9)
Nombre de liens causaux : 9
CausalLink
  α = 0.3653  [C] ◀━━ [A]
CausalLink
  α = 0.8556  [A] ◀━━ [B]
CausalLink
  α = 0.922  [A, B] ◀━━ [C]
CausalLink
  α = 0.3832  [A, B, C] ◀━━ [A, C]
CausalLink
  α = 0.1285  [A, B] ◀━━ [B, C]
CausalLink
  α = 0.8556  [A] ━━▶ [B, C]
CausalLink
  α = 0.8556  [B] ━━▶ [C]
CausalLink
  α = 0.3653  [C] ━━▶ [A]
CausalLink
  α = 0.922  [A, B] ━━▶ [C]

Force alpha : min=0.128, max=0.922, valeurs distinctes=[0.128, 0.365, 0.383, 0.856, 0.922]

Interpretation : la causalite evenementielle discrimine

Contrairement au reseau XOR (ou alpha = 1.0 pour tous les liens), le reseau probabiliste produit des forces alpha variees — ici de l’ordre de 0.13 a 0.92. La causalite evenementielle exerce enfin sa capacite distinctive :

  • Liens forts (alpha eleve, ~0.92) : mecanismes qui determinent leur purview de maniere fiable dans cette transition — les “vraies” causes evenementielles.
  • Liens faibles (alpha bas, ~0.13) : mecanismes dont la contribution causale est marginale, noyee dans le bruit du reseau.

Cette discrimination est la raison d’etre de pyphi.actual : quantifier qui a cause quoi avec une nuance graduee, ce qu’un reseau deterministe (XOR) masque en saturant toutes les forces a la valeur maximale. C’est l’analogue, en causalite evenementielle, du constat deja fait en section 4 sur les etats inaccessibles : la richesse du calcul emergent depend de la structure probabiliste du reseau, pas seulement de sa connectivite.

6.2 Macro-subsystems (coarse-graining, blackboxing)

Le module pyphi.macro permet d’agréger des nœuds (coarse-grain) ou de blackboxer certains éléments. On peut ainsi recalculer \(\Phi\) à une échelle différente, ce qui rejoint l’idée de l’ICT qu’on peut analyser l’information intégrée à divers niveaux.


Exercice : Exploration de la structure cause-effet

Objectifs : 1. Analyser l’impact de la topologie du réseau sur la valeur de Φ 2. Explorer la relation entre mécanismes et purviews 3. Comparer différents réseaux pour comprendre l’intégration

Contexte : Vous avez vu comment calculer Φ pour un réseau XOR simple. L’IIT suggère que la valeur de Φ dépend de la façon dont l’information est intégrée dans le système. Dans cet exercice, vous allez explorer cette relation.

Questions :

  1. Question : Créez un réseau AND (où chaque nœud fait un AND de ses entrées) et comparez sa valeur Φ avec celle du réseau XOR. Quelle topologie produit plus d’intégration ?

  2. Question : Pour le réseau XOR, analysez comment Φ change selon l’état du système. Existe-t-il des états où Φ = 0 ?

  3. Question : Explorez la CES d’un sous-système à 2 nœuds vs le système complet. Comment le nombre de concepts évolue-t-il ?

# Exercice - Exploration de la structure cause-effet
# 1. Créer un réseau AND et comparer Φ avec XOR
# 2. Analyser Φ selon l'état
# 3. Comparer CES 2-noeuds vs 3-noeuds

# Exemple de structure pour le réseau AND
# and_tpm = ...  # Définir la TPM pour un réseau AND
# and_network = pyphi.Network(and_tpm, connectivity_matrix=cm)
# and_subsystem = pyphi.Subsystem(and_network, (0,0,0), (0,1,2))
# print("Φ (AND):", pyphi.compute.phi(and_subsystem))

print("Exercice a completer")  # todo etudiant: implementer
Exercice a completer

Conclusion

Ce notebook a introduit l’Integrated Information Theory (IIT) et son implementation de reference, PyPhi, en suivant le fil qui mene des données brutes d’un système a une mesure de son integration causale.

Le pipeline PyPhi en un coup d’oeil

Étape Objet PyPhi Rôle
Dynamique Network (+ TPM) Decrit comment l’etat du système evolue, mécanisme par mécanisme
Decoupage Subsystem Fixe un etat de fond et isole les éléments analyses
Integration \(\Phi\) Mesure l’irreductibilite du tout a ses parties (la partition qui « coute » le moins)
Structure CES (concepts) Decompose ce que le système distingue : repertoires cause-effet de chaque mécanisme
Recherche complexes / major_complex Applique l’exclusion : trouve le sous-système qui maximise \(\Phi\) — la frontière n’est plus choisie à la main

Le point conceptuel central est que \(\Phi\) ne mesure pas une quantite d’information transmise, mais une information integree : ce qui serait perdu si l’on coupait le système en morceaux. Un système dont les parties sont independantes a un \(\Phi\) nul, même s’il calcule des choses complexes – l’integration, pas la performance, est le critere.

Ce que les exemples ont mis en evidence

  • La TPM (matrice de transition) est le point d’entree obligatoire : toute la causalite en decoule, d’ou l’importance de la validation StateUnreachableError qui protege contre l’analyse d’etats que la dynamique ne peut jamais produire.
  • La CES revele la qualite de l’expérience d’un système (quels concepts il porte), la ou \(\Phi\) n’en donne que la quantite.
  • La causalite actuelle (actual causation) repond a une question différente : non pas « quelle est la structure du système » mais « qu’est-ce qui a effectivement cause cette transition ».
  • Le coarse-graining / blackboxing (macro-subsystems) montre qu’un même substrat peut etre plus integre a une echelle macro qu’a l’echelle micro – l’echelle a laquelle \(\Phi\) est maximal est theoriquement privilegiee.

Limites et suite

Le calcul de \(\Phi\) est combinatoirement explosif (toutes les partitions, tous les mécanismes), ce qui confine PyPhi a de petits systèmes (quelques noeuds) – une contrainte pratique a garder en tete avant toute ambition d’echelle. Les raffinements de la théorie (IIT 4.0, calcul du \(\varphi\) au niveau des distinctions et relations) et les stratégies de passage a l’echelle sont approfondis dans le notebook IIT-2 : Sujets avances.

A retenir : l’IIT propose de mesurer la conscience comme l’integration causale irreductible d’un système sur lui-même. PyPhi rend cette definition operationnelle : on declare une dynamique (TPM), on choisit un decoupage (Subsystem), et le moteur calcule a la fois combien (\(\Phi\)) et quoi (CES) le système integre.

Où va la suite. IIT-2 approfondit le partitionnement (MIP/SIA), les répertoires cause-effet et la comparaison Big \(\Phi\) / Small \(\Phi\) ; IIT-3 traite le coarse-graining et l’échelle du \(\Phi\). Pour relier cette mécanique à la question ICT plus large de la représentation interne et des dissociations : synthese-invariants-dissociations-obstructions.md.

## 7. Ressources complémentaires

Fin du notebook. Merci pour votre attention !

Retour au sommet