et a chaque etat correspond une valeur de \(\Phi\). On obtient donc une trajectoire de \(\Phi\) — le film de l’information integree pendant que le système se deplace dans son espace d’etats.
Trois questions guident le notebook :
Le paysage de \(\Phi\) — comment \(\Phi\) varie-t-il d’un etat a l’autre ? Est-il plat ou accidente ?
La trajectoire de \(\Phi\) — que devient \(\Phi\) le long d’une trajectoire, en particulier sur l’attracteur ou le système finit par tourner ?
La robustesse de \(\Phi\) — après une perturbation, la trajectoire (et donc le profil de \(\Phi\)) se reconstitue-t-elle ?
On utilise le vrai moteur PyPhi pour tous les calculs de \(\Phi\) : aucun substitut, aucune valeur fabriquee. Les systèmes restent volontairement minuscules (3 noeuds, \(2^3 = 8\) etats) car le calcul de \(\Phi\) est exponentiel — c’est une contrainte intrinseque de l’IIT, discutee en conclusion.
Statut épistémique — Établi : La trajectoire de Φ oscille sans asymptote unique sur l’attracteur. Portée et détail dans la matrice de dissociations.
Repères IIT canoniques
Vocabulaire de référence pour rattacher ce notebook à la colonne vertébrale de la série (mandat user 2026-07-03, directive 1 — recadrage IIT/\(\Phi\)). Sources : IIT-01-IntroToPyPhi (fondations), ICT-0-Framing § « Double lecture du sigle », ICT-0-Annexe (texte fondateur, \(\Phi_{\text{dyn}}\)).
Trois notions de Integrated Information Theory (Tononi, 2004/2008) suffisent pour ce qui suit :
\(\Phi\) (information intégrée, integrated information) — la quantité d’information qu’un système génère par le fait d’être un, au-dessus et au-delà de ses parties prises indépendantes. Calcul canonique : \(\Phi = \min_{\text{partition } p} \mathrm{EMD}(p)\) — l’information qui ne peut pas être factorisée sur la partition qui sépare le système en parties indépendantes (Minimum Information Partition, MIP). Implémentation de référence : PyPhi, utilisé tel quel ici (aucun substitut).
TPM (Transition Probability Matrix) — la matrice de transition conditionnelle du système sous-jacent. Au format state-by-node de PyPhi, network.tpm est un tableau (N, N) indicé par un tuple d’état : pour chaque état présent (ligne), la probabilité de chaque état futur (colonne). Pour les réseaux déterministes de ce notebook, chaque ligne a un seul 1 (et des 0 ailleurs).
Partition — une bipartition du système en deux sous-systèmes \(\mathcal{S}^L \cup \mathcal{S}^R = \mathcal{S}\) avec \(\mathcal{S}^L \cap \mathcal{S}^R = \varnothing\). Pour la cause-effect information (\(\Phi_{\mathrm{CE}}\)), PyPhi balaie toutes les bipartitions dirigées et retient celle qui minimise la divergence entre le système entier et la somme de ses parties (MIP).
Ce que ce notebook ajoute : \(\Phi\) est habituellement calculé état par état, comme une photographie. ICT-1 le filme — on suit la trajectoire de \(\Phi\) le long de \(s_0 \rightarrow s_1 \rightarrow s_2 \rightarrow \dots\) pendant que le système déterministe évolue dans son espace d’états. Les trois questions (paysage, trajectoire, robustesse) sont les projections naturelles de \(\Phi\) sur la dimension temporelle.
Pourquoi 3 nœuds. Le calcul exact de \(\Phi\) est exponentiel en \(N\) (chaque bipartition d’un système à \(N\) nœuds \(= 2^{N-1} - 1\) coupes dirigées, plus le calcul EMD par coupe). Pour \(N = 3\) on a \(3\) bipartitions, et PyPhi termine en quelques millisecondes ; pour \(N = 10\) le calcul devient intraitable — c’est la contrainte intrinsèque discutée en conclusion, et la motivation des approximations introduites par les strates suivantes (\(\Phi_{\text{dyn}}\) dans l’annexe fondatrice, \(\Phi\)-trajectoires échantillonnées, mesures variationnelles).
0. Mise en place
On charge PyPhi et le module ict.trajectories (pose a cote de PyPhi, il gere l’evolution dans l’espace d’etats sans dependre de PyPhi). On desactive le parallelisme de PyPhi (sous Windows le multiprocessing provoque sinon une avalanche de processus) et la validation des etats accessibles, pour pouvoir calculer \(\Phi\) sur tous les etats, y compris transitoires. Deux roles bien distincts dans ce notebook :
PyPhi calcule \(\Phi\) : pour un etat donne, il considere toutes les facons de couper le reseau en deux parties, evalue pour chaque coupe la perte d’information cause et effet, et retient la coupe minimale — le minimum information partition (MIP). \(\Phi\) est l’information qui survit a la meilleure coupe : c’est le calcul couteux du notebook, fait une fois pour toutes en section 1.
ict.trajectories ne connait que la TPM : il fait avancer les etats, detecte attracteurs et bassins, sans jamais dependre de PyPhi. Les sections 2 et 3 ne font que relire la carte de \(\Phi\) precalculee.
import warningswarnings.filterwarnings("ignore", message=".*pkg_resources is deprecated.*", category=UserWarning)import sys, osimport numpy as npimport matplotlib.pyplot as plt# le package ict est dans le dossier parent du notebooksys.path.insert(0, os.path.abspath("."))from ict import trajectories as Timport pyphipyphi.config.PROGRESS_BARS =Falsepyphi.config.WELCOME_OFF =True# calculer Phi pour n'importe quel etat (meme inaccessible / transitoire)pyphi.config.VALIDATE_SUBSYSTEM_STATES =False# desactiver le parallelisme (Windows : evite l'avalanche de processus)for _attr in ("PARALLEL_CONCEPT_EVALUATION", "PARALLEL_CUT_EVALUATION","PARALLEL_COMPLEX_EVALUATION", "PARALLEL"):ifhasattr(pyphi.config, _attr):setattr(pyphi.config, _attr, False)print("PyPhi", pyphi.__version__, "| numpy", np.__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 1.2.0 | numpy 1.26.4
Deux reseaux a 3 noeuds
On compare deux architectures causales déterministes, toutes deux integrees mais de structures très différentes :
AND/OR : un petit reseau ou les noeuds combinent leurs entrees par des portes logiques mixtes.
XOR : l’exemple canonique de PyPhi, ou chaque noeud est le OU-exclusif des deux autres — un reseau très symetrique.
Pour chacun, network.tpm est la matrice de transition au format state-by-node de PyPhi : indexee par un tuple d’etat, elle donne l’etat suivant (les reseaux sont déterministes). La cellule suivante affiche aussi la connectivite des deux reseaux, et l’opposition est nette : chez AND/OR la matrice est pleine (chaque noeud ecoute les deux autres, portes logiques mixtes oblige), chez XOR c’est un anneau sans auto-connexion (diagonale nulle). Deux choix deliberes : la connexion dense autorise des interactions asymetriques — donc des etats aux roles tres inegaux — tandis que l’anneau parfaitement symetrique traite les etats de facon interchangeable.
Trois noeuds, c’est le plus petit systeme ou la question devient interessante
a deux elements une seule coupe est possible et le MIP est trivial ; a trois, les partitions se diversifient et \(\Phi\) peut vraiment varier d’un etat a l’autre — le relief que la section 1 va mesurer.
\(\Phi\) depend du système et de son etat. Avant de parler de trajectoire, cartographions \(\Phi\) sur tous les etats : c’est le paysage de \(\Phi\).
La fonction ci-dessous calcule \(\Phi\) pour chacun des \(2^n\) etats. C’est le seul endroit ou PyPhi travaille vraiment ; le reste du notebook ne fait que relire cette carte. Concretement, chaque appel de \(\Phi\) est lourd : pour l’etat considere, PyPhi examine toutes les partitions du systeme en deux parties, construit pour chacune les repertoires de cause et d’effet (les distributions de passe et de futur compatibles avec la mecanique du reseau), et retient la coupe qui detruit le moins d’information. Ici \(2^3 = 8\) etats a cartographier, c’est jouable ; la meme carte a \(2^5\) etats demanderait deja quatre fois plus d’appels.
La sortie texte donne les valeurs exactes, la figure qui suit en donne la lecture visuelle — huit barres par reseau. Tout le reste du notebook (les trajectoires de la section 2, la robustesse de la section 3) relira cette carte sans plus jamais invoquer PyPhi.
def phi_landscape(network, n):# carte etat (tuple) -> Phi (float), calculee par PyPhi sur tous les etats nodes =tuple(range(n)) carte = {}for s in T.all_states(n): sub = pyphi.Subsystem(network, s, nodes) carte[s] =float(pyphi.compute.phi(sub))return carteland_andor = phi_landscape(andor, 3)land_xor = phi_landscape(xor, 3)def resume(carte, nom): v =list(carte.values())print(f"{nom:8s} min={min(v):.4f} max={max(v):.4f} "f"moyenne={sum(v)/len(v):.4f} amplitude={max(v)-min(v):.4f}")for s in T.all_states(3):print(f" etat {s} : AND/OR Phi={land_andor[s]:.4f} XOR Phi={land_xor[s]:.4f}")print()resume(land_andor, "AND/OR")resume(land_xor, "XOR")
# visualisation des deux paysages de Philabels = ["".join(map(str, s)) for s in T.all_states(3)]x = np.arange(8)fig, axes = plt.subplots(1, 2, figsize=(12, 4), sharey=True)for ax, carte, nom, col in [(axes[0], land_andor, "AND/OR", "#2c7fb8"), (axes[1], land_xor, "XOR", "#d95f0e")]: vals = [carte[s] for s in T.all_states(3)] ax.bar(x, vals, color=col) ax.set_title(f"Paysage de $\\Phi$ — {nom}") ax.set_xlabel("etat (ABC)") ax.set_xticks(x); ax.set_xticklabels(labels, rotation=45) ax.axhline(np.mean(vals), color="gray", ls="--", lw=1, label=f"moyenne {np.mean(vals):.2f}") ax.legend()axes[0].set_ylabel("$\\Phi$")fig.suptitle("Le paysage de $\\Phi$ : accidente (AND/OR) vs plat (XOR)")plt.tight_layout()plt.show()
Lecture. Les deux paysages sont radicalement différents :
AND/OR est accidente : la plupart des etats ont un \(\Phi\) faible (\(\approx 0{,}19\)), mais un etat — 100, soit \(A\) seul actif — culmine a \(\Phi \approx 2{,}31\), et 101 forme un relief intermediaire (\(\approx 0{,}69\)). L’information integree du système depend fortement de l’etat ou il se trouve.
XOR est un plateau : \(\Phi = 1{,}875\) pour tous les etats (amplitude nulle). La symetrie du OU-exclusif rend l’information integree independante de l’etat.
Un même nombre de noeuds, deux “personnalites causales” opposees. C’est ce paysage qui va donner son relief aux trajectoires de \(\Phi\).
2. La trajectoire de \(\Phi\)
Un reseau déterministe, lache depuis un etat de depart, suit un chemin unique jusqu’a tomber dans un attracteur (un point fixe ou un cycle). Le module ict.trajectories reconstruit ce chemin en lisant la TPM, puis on relit la carte de \(\Phi\) le long du chemin. Un argument de structure precede toute simulation : l’espace d’etats est fini (\(2^3 = 8\) etats) et la dynamique deterministe — chaque etat a un successeur unique. Un chemin qui visite au neuvieme pas un etat deja visite (principe des tiroirs) est donc condamne a boucler : toute trajectoire se decompose en un transitoire suivi d’un attracteur (point fixe ou cycle).
Les quatre departs choisis balayent les cas de figure : l’un demarre sur le cycle, deux autres le rejoignent apres des transitoires de longueurs differentes, et le dernier est l’exception qui tombe dans le point fixe isole. La sortie affiche pour chacun le chemin, l’attracteur atteint, la courbe de \(\Phi\), puis le recensement des bassins — c’est ce recensement qui dit si la destination depend du depart ou de la structure du paysage.
# trajectoires de quelques etats de depart du reseau AND/ORdeparts = [(0, 0, 0), (1, 0, 0), (0, 1, 0), (1, 1, 1)]for start in departs: path, debut = T.state_trajectory(andor.tpm, start) attr = T.attractor_of(andor.tpm, start) phis = T.phi_trajectory(land_andor, path)print(f"depart {start} -> chemin {path}")print(f" attracteur {attr}")print(f" Phi le long : {[round(p, 3) for p in phis]}\n")print("Bassins d'attraction (signature attracteur -> nb d'etats de depart) :")print(" AND/OR :", T.basin_sizes(andor.tpm, 3))print(" XOR :", T.basin_sizes(xor.tpm, 3))
et la trajectoire de \(\Phi\) sur ce cycle pulse : \(2{,}31 \rightarrow 0{,}19
\rightarrow 0{,}19 \rightarrow 2{,}31 \dots\) L’information integree n’est donc pas un badge fixe du système : elle monte en fleche un pas sur trois, quand le système traverse l’etat-pic 100, puis retombe. Un système dynamique peut etre “fortement integre par intermittence”.
Le huitieme etat, 000, est un point fixe isole (\(\Phi\) constant a \(0{,}19\)) : un petit bassin a part. Sur XOR, au contraire, toutes les trajectoires atteignent un point fixe en un ou deux pas, et comme le paysage est plat, \(\Phi\) y reste rigoureusement constant a \(1{,}875\) : un film sans relief. Le recensement des bassins complete le tableau : AND/OR separe ses huit etats de depart en \(1 + 7\) — le point fixe 000 n’attire que lui-meme, le cycle capte tout le reste. XOR, lui, eclate en quatre points fixes de bassin 2 chacun (000, 011, 101, 110) : la symetrie du OU-exclusif se retrouve jusque dans la geographie des bassins, exactement comme elle fabriquait le plateau de \(\Phi\).
3. Robustesse : \(\Phi\) après une perturbation
La serie ICT s’interesse aux trajectoires qui se reparent (cf. ICT-2/ICT-3 pour le toy-model de tri). Posons ici la même question avec de l’information integree reelle : si l’on perturbe le système une fois installe sur son attracteur, sa trajectoire de \(\Phi\) se reconstitue-t-elle ?
On part du cycle AND/OR, on bascule un seul noeud (perturbation 1-bit), puis on laisse le système re-evoluer. Pourquoi ce protocole exactement : l’etat de reference 110 est sur le cycle — on perturbe le regime asymptotique du systeme, pas un transitoire qui aurait de toute facon disparu. Le flip d’un seul bit est la perturbation minimale qui puisse rediriger une dynamique deterministe : moindre, rien ne change ; plus forte, on ne saurait plus attribuer un eventuel changement de destination a la fragilite du systeme plutot qu’a la violence du coup. La cellule suivante essaie les trois flips possibles (noeuds 0, 1 et 2) et compare, pour chacun, l’attracteur atteint a l’attracteur de reference.
# perturbation 1-bit a partir d'un etat de l'attracteur, puis recuperationattr = T.attractor_of(andor.tpm, (1, 1, 1))base = attr[0] # un etat du cycle attracteurprint(f"attracteur de reference : {attr}")print(f"etat perturbe : {base}\n")fig, ax = plt.subplots(figsize=(10, 5))for bit inrange(3): pert = T.flip_bit(base, bit) path, _ = T.state_trajectory(andor.tpm, pert)# on prefixe l'etat de base pour visualiser le 'saut' de la perturbation phis = T.phi_trajectory(land_andor, [base] + path) retour =tuple(sorted(T.attractor_of(andor.tpm, pert))) ==tuple(sorted(attr))print(f"flip noeud {bit} : {base} -> {pert} | "f"retour au meme attracteur : {retour}") ax.plot(range(len(phis)), phis, marker="o", label=f"flip noeud {bit} ({''.join(map(str, base))}->{''.join(map(str, pert))})")ax.axvline(0.5, color="red", ls=":", lw=1, label="perturbation")ax.set_title("Recuperation de la trajectoire de $\\Phi$ apres perturbation 1-bit")ax.set_xlabel("pas de temps (0 = etat avant perturbation)")ax.set_ylabel("$\\Phi$")ax.legend()ax.grid(alpha=0.3)plt.tight_layout()plt.show()
Lecture. Les trois perturbations 1-bit possibles depuis l’etat 110 du cycle ramenent le système au même attracteur : la trajectoire de \(\Phi\) se reconstitue dans tous les cas. Le profil pulsatile de \(\Phi\) est donc une propriete robuste de la dynamique, pas un accident de l’etat initial.
Les chemins de retour différent : basculer le noeud 1 (110 \to 100) place directement le système sur l’etat-pic, et \(\Phi\) remonte immediatement a \(2{,}31\) ; basculer le noeud 0 (110 \to 010) impose un detour plus long avant que le pic ne revienne. La forme du transitoire depend de la perturbation, mais la destination — le cycle et son profil de \(\Phi\) — est invariante.
4. Exercices
Trois exercices pour manipuler vous-même le paysage et les trajectoires de \(\Phi\). Completez le corps de chaque fonction (remplacez le return None). Le notebook s’execute de bout en bout même sans les completer. Les trois exercices se repondent : le premier relit le paysage (statique — l’amplitude mesuree en section 1 : \(2{,}125\) pour AND/OR, \(0\) pour XOR), le deuxieme resume un cycle par un seul nombre (la moyenne de \(\Phi\) sur le cycle AND/OR vaut \(\approx 0{,}90\), contre \(1{,}875\) sur le plateau de XOR), le troisieme detecte les evenements de la courbe (un pic de \(\Phi\) tous les trois pas sur le cycle AND/OR). Chaque fonction retourne un nombre, et la cellule qui suit chaque stub affiche la valeur attendue quand le resultat n’est pas None — vous pouvez verifier votre reponse sans attendre la correction.
Exercice 1 — Amplitude du paysage de \(\Phi\)
Le paysage AND/OR est accidente (amplitude \(\approx 2{,}12\)), celui de XOR est plat (amplitude \(0\)). Ecrivez une fonction qui mesure cet ecart : l’amplitude \(\max \Phi - \min \Phi\) sur tout le paysage d’un reseau. La moyenne de \(\Phi\) sur tout le paysage AND/OR (\(\approx 0{,}52\) d’apres la section 1) dit autre chose que l’amplitude : deux reseaux peuvent avoir la meme moyenne et des personnalites causales opposees. L’amplitude est la bonne statistique pour separer « accidente » de « plat ».
def phi_amplitude(network, n):# Objectif : retourner max(Phi) - min(Phi) sur les 2**n etats du reseau.# Indice : reutiliser phi_landscape(network, n) defini en section 1.# Indice : amplitude = max(carte.values()) - min(carte.values()).# Etape etudiant : result =None# TODO etudiant : calculer l'amplitude du paysagereturn resultamp = phi_amplitude(andor, 3)if amp isnotNone:print(f"Amplitude du paysage AND/OR : {amp:.4f}")else:print("Exercice a completer")
Exercice a completer
Exercice 2 — \(\Phi\) moyen sur l’attracteur
Sur le cycle AND/OR, \(\Phi\) pulse (\(2{,}31 \to 0{,}19 \to 0{,}19\)). Le \(\Phi\)moyen sur l’attracteur resume “l’integration soutenue” d’un regime. Ecrivez une fonction qui, depuis un etat de depart, retourne la moyenne de \(\Phi\) sur les etats du cycle attracteur. Avec les valeurs de la section 2, la reponse attendue pour un depart sur le cycle AND/OR est \((2{,}312 + 0{,}188 + 0{,}188) / 3 \approx 0{,}896\) : la moyenne masque la pulsation — c’est precisement ce que ce chiffre apprend, par contraste avec le \(1{,}875\) constant du plateau XOR.
def mean_phi_on_attractor(tpm, phi_map, start):# Objectif : moyenne de Phi sur les etats de l'attracteur atteint depuis start.# Indice : attr = T.attractor_of(tpm, start) donne la liste des etats du cycle.# Indice : phi_map[etat] donne Phi de chaque etat ; faire la moyenne.# Etape etudiant : result =None# TODO etudiant : moyenne de Phi sur l'attracteurreturn resultm = mean_phi_on_attractor(andor.tpm, land_andor, (1, 1, 1))if m isnotNone:print(f"Phi moyen sur l'attracteur AND/OR : {m:.4f}")else:print("Exercice a completer")
Exercice a completer
Exercice 3 — Compter les pulsations de \(\Phi\)
Un “pic” de \(\Phi\) le long d’une trajectoire est un maximum local. La fonction T.detect_events renvoie les indices des minima et maxima locaux d’une courbe. Ecrivez une fonction qui compte le nombre de pics de \(\Phi\) le long de la trajectoire issue d’un etat de depart. Sur le cycle AND/OR, la courbe de \(\Phi\) monte en fleche un pas sur trois : le nombre de pics croit donc lineairement avec la longueur de simulation, tandis qu’une trajectoire du point fixe 000 n’en montre aucun. Deux dynamiques, deux signatures — c’est ce contraste que votre compteur doit reproduire.
def count_phi_pulses(tpm, phi_map, start):# Objectif : nombre de maxima locaux (pics de Phi) le long de la trajectoire.# Indice : path, _ = T.state_trajectory(tpm, start) donne le chemin d'etats.# Indice : phis = T.phi_trajectory(phi_map, path) donne la courbe de Phi.# Indice : T.detect_events(phis)["maxima"] est la liste des indices de pics.# Etape etudiant : result =None# TODO etudiant : compter les pics (len des maxima)return resultn_pulses = count_phi_pulses(andor.tpm, land_andor, (0, 1, 0))if n_pulses isnotNone:print(f"Nombre de pics de Phi (depart 010) : {n_pulses}")else:print("Exercice a completer")
Exercice a completer
Conclusion
En appliquant la notion de trajectoire a \(\Phi\) lui-même, on a vu que :
\(\Phi\) a un paysage. Pour un même nombre de noeuds, ce paysage peut etre accidente (AND/OR : un pic a \(2{,}31\), un relief intermediaire a \(0{,}69\), le reste a \(0{,}19\)) ou parfaitement plat (XOR : \(1{,}875\) partout). L’information integree est une fonction de l’etat, pas seulement du cablage.
\(\Phi\) a une trajectoire. Sur l’attracteur d’AND/OR — un cycle de longueur 3 — \(\Phi\)pulse. Un système peut donc etre integre par intermittence : l’integration est un événement dans le temps, pas une etiquette permanente.
La trajectoire de \(\Phi\) est robuste. Les trois perturbations 1-bit du cycle ramenent au même attracteur : le profil pulsatile de \(\Phi\) se repare.
La limite intrinseque. Tous les systèmes de ce notebook ont 3 noeuds. Le calcul de \(\Phi\) est super-exponentiel : il faut considerer toutes les partitions du système pour chaque etat. Au-dela d’une dizaine de noeuds, PyPhi devient impraticable. C’est la raison pour laquelle l’IIT exacte se cantonne aux petits systèmes — et c’est le problème que les notebooks suivants attaquent :
ICT-5/ICT-6 : trouver une macro-description (coarse-graining, pont tri \(\to\) TPM) ou \(\Phi\) redevient calculable a plus grande echelle.
ICT-2/ICT-3 : etudier des trajectoires riches sur des modèles jouets (tri auto-organise) qui se passent de PyPhi, quand le système est trop grand pour l’IIT exacte.
Conformement au principe de la serie (ICT-0) : tout ce qui precede a ete calcule par le vrai PyPhi, puis narre tel quel.