ICT-5 : Émergence causale — quelle échelle décrit le mieux un système ?
Série ICT (Integrated Causal Trajectories), Epic #4588. Voir le cadrage ICT-0. Ce notebook met en œuvre le second pilier de la série : la recherche systématique des échelles causales pertinentes (Jansma & Hoel, Engineering Emergence, 2025).
La série IIT mesure \(\Phi\) — l’information intégrée — pour un système à une échelle donnée (les micro-nœuds). Mais une question reste ouverte : est-ce la bonne échelle ?
Un même système dynamique peut être décrit au niveau micro (chaque nœud) ou macro (des groupes de nœuds agrégés). L’émergence causale désigne le fait, contre-intuitif, que la description macro peut être causalement plus forte que la micro : plus déterministe, moins dégénérée, plus intégrée. On parle alors d’émergence quand
\[\Phi_{\text{macro}} > \Phi_{\text{micro}}.\]
Ce notebook utilise le vrai outil SOTA — le module pyphi.macro (coarse-graining, effective_info, emergence) de PyPhi — pour :
mesurer \(\Phi\) et l’information effective (EI) à l’échelle micro ;
chercher l’échelle macro qui maximise \(\Phi\) (toutes les agrégations possibles) ;
montrer que l’émergence n’est pas automatique : sur certains systèmes elle est franche, sur d’autres le coarse-graining ne fait que détruire de l’information.
Pré-requis. Ce notebook calcule des \(\Phi\) réels avec PyPhi : il s’exécute avec le noyau Python 3 (PyPhi/IIT) (env conda pyphi). Cf IIT-01-IntroToPyPhi.
Statut épistémique — Établi : L’émergence causale (Hoel) est discriminante entre échelles. Portée et détail dans la matrice de dissociations.
import warningswarnings.filterwarnings("ignore", message=".*pkg_resources is deprecated.*", category=UserWarning)# Configuration de PyPhi AVANT tout calcul.# Sous Windows, le multiprocessing de PyPhi provoque une avalanche de processus :# on desactive tout parallelisme (calcul mono-thread, suffisant pour <=4 noeuds).import numpy as npimport matplotlib.pyplot as pltimport pyphipyphi.config.WELCOME_OFF =Truepyphi.config.PROGRESS_BARS =False# Calculer Phi sur tous les etats, y compris transitoires/inaccessibles.pyphi.config.VALIDATE_SUBSYSTEM_STATES =Falsefor _attr in ("PARALLEL_CONCEPT_EVALUATION", "PARALLEL_CUT_EVALUATION","PARALLEL_COMPLEX_EVALUATION", "PARALLEL"):ifhasattr(pyphi.config, _attr):setattr(pyphi.config, _attr, False)import pyphi.macro as Mprint("PyPhi", pyphi.__version__, "| NumPy", np.__version__)print("Calcul mono-thread (parallelisme PyPhi desactive).")
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
Calcul mono-thread (parallelisme PyPhi desactive).
1. Mesurer la causalité à l’échelle micro
On part de l’exemple d’émergence canonique livré avec PyPhi (pyphi.examples.macro_network()) : un micro-système de 4 nœuds binaires dont la dynamique micro est volontairement dégénérée (bruitée). On mesure d’abord, au niveau micro :
\(\Phi\) — l’information intégrée du système dans son état courant ;
EI (effective information) — la réduction d’incertitude qu’apporte la connaissance de l’état présent sur l’état futur, moyennée sur l’ensemble du répertoire de causes possibles.
Une EI faible signale une dynamique dégénérée : beaucoup d’états passés mènent au même futur, le mécanisme « perd » de l’information.
Le micro-système n’a qu’un \(\Phi\) faible (~0,11) : pris nœud par nœud, le système n’intègre presque pas d’information. Son EI (~1,15 bit) reflète une dynamique dégénérée — c’est exactement le terrain où l’émergence peut se manifester. Reste à savoir si une description plus grossière ferait mieux.
2. Chercher l’échelle émergente (coarse-graining)
pyphi.macro.emergence énumère toutes les agrégations (coarse-grainings) possibles des micro-nœuds en macro-nœuds, calcule \(\Phi\) pour chacune, et retient l’échelle qui maximise\(\Phi_{\text{macro}}\). L’émergence est alors
Une valeur positive signifie que la description macro est causalement plus forte que la micro.
# Recherche de la meilleure echelle macro (coarse-graining uniquement).mn = M.emergence(net, state, do_coarse_grain=True, do_blackbox=False)macro_phi =float(mn.phi)delta =float(mn.emergence)print(f"Phi_micro = {micro_phi:.4f}")print(f"Phi_macro (max)= {macro_phi:.4f}")print(f"emergence = {delta:+.4f}")print(f"meilleure agregation (partition micro -> macro) : {mn.coarse_grain.partition}")print(f" -> les {net.size} micro-noeuds sont regroupes en {len(mn.coarse_grain.partition)} macro-noeuds")
Phi_micro = 0.1139
Phi_macro (max)= 0.5972
emergence = +0.4833
meilleure agregation (partition micro -> macro) : ((0, 1), (2, 3))
-> les 4 micro-noeuds sont regroupes en 2 macro-noeuds
Interprétation : l’émergence est réelle
Le coarse-graining qui regroupe les nœuds ((0, 1), (2, 3)) — soit 2 macro-nœuds — porte \(\Phi\) de ~0,11 à ~0,60 : une émergence d’environ +0,48. À l’échelle macro, ce qui semblait un agrégat bruité de 4 nœuds devient un système nettement plus intégré. Le « bon » niveau de description causale de ce système n’est pas le micro : c’est le macro.
C’est le résultat central de Hoel : agréger peut augmenter l’information causale, parce que la macro-dynamique est plus déterministe et moins dégénérée que la micro.
# Figure 1 : Phi micro vs macro pour le systeme emergent.fig, ax = plt.subplots(figsize=(6, 4.2))bars = ax.bar(["micro\n(4 noeuds)", "macro\n(2 noeuds)"], [micro_phi, macro_phi], color=["#9aa7b3", "#2a8a5f"], width=0.55)ax.set_ylabel(r"$\Phi$(information integree)")ax.set_title("Emergence causale : la description macro est plus integree")for b, v inzip(bars, [micro_phi, macro_phi]): ax.text(b.get_x() + b.get_width()/2, v +0.01, f"{v:.3f}", ha="center", va="bottom")ax.annotate(f"emergence\n{delta:+.3f}", xy=(1, macro_phi), xytext=(0.30, macro_phi*0.55), ha="center", arrowprops=dict(arrowstyle="->", color="#2a8a5f"), color="#2a8a5f", fontweight="bold")ax.set_ylim(0, macro_phi*1.18)plt.tight_layout()plt.show()
Lecture de la figure. Le diagramme rend visible ce que le texte numérique énonçait : deux barres – \(\Phi_{\text{micro}} = 0{,}114\) (gris) contre \(\Phi_{\text{macro}} = 0{,}597\) (vert) – et l’annotation fléchée \(+0{,}483\) qui les relie. Le rapport de hauteur des barres est le message : la description macro est environ cinq fois plus intégrée que la micro, et l’écart visuel entre les deux barres correspond exactement au gain d’émergence. C’est la même mesure que la cellule précédente, vue cette fois d’un seul coup d’œil – l’argument de Hoel est un argument de contraste, et le contraste se montre mieux qu’il ne se déclare.
3. L’émergence n’est pas automatique : deux contre-exemples
Si agréger augmentait toujours\(\Phi\), l’émergence n’aurait aucun contenu informatif. Vérifions sur deux systèmes dont la micro-dynamique est déjà fortement intégrée :
le réseau XOR à 3 nœuds (chaque nœud = XOR des deux autres) ;
le basic_network de PyPhi (le système OR/AND/XOR de référence, \(\Phi_{\text{micro}}=2{,}31\)).
Pour ces systèmes, on s’attend à ce que le coarse-graining détruise de l’information (émergence négative) : la bonne échelle reste le micro.
# On reutilise la meme analyse sur deux systemes micro-integres.def emergence_report(network, st, label): mphi =float(pyphi.compute.phi(pyphi.Subsystem(network, st, range(network.size)))) macro = M.emergence(network, st, do_coarse_grain=True, do_blackbox=False)returndict(label=label, micro_phi=mphi, macro_phi=float(macro.phi), emergence=float(macro.emergence), partition=macro.coarse_grain.partition)rows = [dict(label="Hoel macro_network", micro_phi=micro_phi, macro_phi=macro_phi, emergence=delta, partition=mn.coarse_grain.partition), emergence_report(pyphi.examples.xor_network(), (0, 0, 0), "XOR"), emergence_report(pyphi.examples.basic_network(), (1, 0, 0), "basic_network"),]print(f"{'systeme':22s}{'Phi_micro':>10s}{'Phi_macro':>10s}{'emergence':>11s} partition")for r in rows:print(f"{r['label']:22s}{r['micro_phi']:10.4f}{r['macro_phi']:10.4f} "f"{r['emergence']:+11.4f}{r['partition']}")
Lecture du tableau : les partitions, colonne oubliée. La dernière colonne mérite la même attention que les signes. Pour XOR, le meilleur coarse-graining trouvé est \(((0, 1),)\) – fusionner tous les nœuds en un macro-nœud unique – et même cette fusion totale échoue : \(\Phi\) passe de \(1{,}8750\) à \(0{,}5000\) (\(-1{,}3750\)). Pour basic_network, \(((0,), (1, 2))\) – une partition mixte un-plus-deux – dégrade pareillement de \(2{,}3125\) à \(0{,}5208\) (\(-1{,}7917\)). Le point décisif : l’algorithme cherchait le meilleur macro pour chaque système. Pour ces deux-là, le meilleur disponible reste une destruction massive de l’intégration – « pas d’émergence » ne signifie donc pas « on n’a pas assez cherché », c’est le verdict que même l’agrégation optimale abîme une intégration déjà présente au micro. Et le miroir se lit en première ligne : le seul système à émergence positive (\(+0{,}4833\)) est précisément celui dont le micro était le moins intégré (\(0{,}1139\)) – l’émergence causale est la propriété des micros pauvres, pas des micros riches.
# Figure 2 : comparaison micro vs macro sur les trois systemes.labels = [r["label"] for r in rows]micro_vals = [r["micro_phi"] for r in rows]macro_vals = [r["macro_phi"] for r in rows]x = np.arange(len(labels))w =0.38fig, ax = plt.subplots(figsize=(8.2, 4.4))ax.bar(x - w/2, micro_vals, w, label=r"$\Phi_{micro}$", color="#9aa7b3")ax.bar(x + w/2, macro_vals, w, label=r"$\Phi_{macro}$", color="#2a8a5f")for i, r inenumerate(rows): col ="#2a8a5f"if r["emergence"] >0else"#b34a4a" ax.text(i, max(r["micro_phi"], r["macro_phi"]) +0.05,f"{r['emergence']:+.2f}", ha="center", color=col, fontweight="bold")ax.set_xticks(x); ax.set_xticklabels(labels)ax.set_ylabel(r"$\Phi$")ax.set_title("Emergence (vert) vs degradation (rouge) selon le systeme")ax.legend()plt.tight_layout()plt.show()
Interprétation : l’émergence est une propriété discriminante
Le contraste est net :
Système
\(\Phi_{\text{micro}}\)
\(\Phi_{\text{macro}}\)
émergence
Hoel macro_network
~0,11
~0,60
+0,48
XOR
~1,88
~0,50
−1,38
basic_network
~2,31
~0,52
−1,79
Pour XOR et basic_network, la micro-dynamique est déjà maximalement intégrée : tout regroupement perd de l’information (émergence franchement négative). Le bon niveau de description y est le micro.
L’émergence n’est donc pas un artefact systématique du coarse-graining : c’est une propriété du système. C’est précisément ce qui rend l’outil intéressant — il distingue les systèmes qui émergent de ceux qui n’émergent pas, au lieu de répondre « oui » partout (un test dégénéré n’aurait aucune valeur).
4. Pourquoi le macro gagne : déterminisme contre dégénérescence
L’information effective se décompose (Hoel) en deux termes :
L’émergence se produit quand l’agrégation augmente le déterminisme et/ou réduit la dégénérescence plus vite qu’elle ne réduit le nombre d’états. Comparons l’EI micro à l’EI du meilleur macro-réseau trouvé.
# EI a l'echelle macro emergente, via le reseau macro reconstruit par PyPhi.macro_network_obj = mn.network ifhasattr(mn, "network") elseNone# mn.network est le micro-reseau ; on reconstruit le macro via le coarse-grain retenu.macro_sub = mn.macro_subsystem ifhasattr(mn, "macro_subsystem") elseNone# Information effective du micro vs nombre d'etats accessibles.print(f"micro : {net.size} noeuds -> {2**net.size} micro-etats ; EI = {micro_ei:.4f} bits")n_macro =len(mn.coarse_grain.partition)print(f"macro : {n_macro} noeuds -> {2**n_macro} macro-etats")print(f"gain de Phi en passant au macro : {delta:+.4f}")print()print("Lecture : le macro a moins d'etats mais une dynamique plus deterministe ;")print("le supplement d'integration (Phi) survit a la perte d'etats -> emergence.")
micro : 4 noeuds -> 16 micro-etats ; EI = 1.1486 bits
macro : 2 noeuds -> 4 macro-etats
gain de Phi en passant au macro : +0.4833
Lecture : le macro a moins d'etats mais une dynamique plus deterministe ;
le supplement d'integration (Phi) survit a la perte d'etats -> emergence.
Interprétation
Le macro-réseau ne compte que 4 états (2 nœuds) contre 16 au micro, et pourtant son \(\Phi\) est cinq fois plus grand. Le gain de déterminisme l’emporte sur la perte de résolution : agréger révèle une structure causale que la description micro noyait dans le bruit. C’est le cœur opérationnel de l’« ingénierie de l’émergence » de Jansma & Hoel — et un pont direct vers la question des trajectoires multi-échelles que la série ICT explore.
5. Exercices
Les trois exercices ci-dessous prolongent l’analyse. Complétez le corps des fonctions (remplacez return None). Le notebook s’exécute de bout en bout même si les exercices ne sont pas faits.
Exercice 1 — Score d’émergence normalisé
L’émergence brute \(\Phi_{\text{macro}} - \Phi_{\text{micro}}\) dépend de l’échelle des \(\Phi\). Un score relatif est plus comparable entre systèmes.
Indice 2 : attention à la division par un \(\Phi_{\text{micro}}\) proche de 0.
def emergence_ratio(network, state):# Etape 1 : Phi_micro = pyphi.compute.phi(Subsystem(network, state, range(network.size)))# Etape 2 : mn = M.emergence(network, state, do_coarse_grain=True)# Etape 3 : renvoyer mn.phi / Phi_micro (gerer Phi_micro ~ 0) result =None# TODO etudiantreturn result# Test (decommenter une fois implemente) :# print("ratio Hoel :", emergence_ratio(pyphi.examples.macro_network(), (0, 0, 0, 0)))print("Exercice 1 a completer")
Exercice 1 a completer
Exercice 2 — L’émergence dépend-elle de l’état ?
L’émergence est calculée pour un état donné. Un même réseau peut émerger dans certains états et pas dans d’autres.
Implémentez emergence_by_state(network) qui renvoie un dictionnaire {état: émergence} pour tous les états du réseau (utilisez pyphi.utils.all_states(network.size)).
Indice : bouclez sur all_states, appelez M.emergence pour chacun, stockez mn.emergence.
def emergence_by_state(network):# Etape 1 : parcourir pyphi.utils.all_states(network.size)# Etape 2 : pour chaque etat, mn = M.emergence(network, etat, do_coarse_grain=True)# Etape 3 : renvoyer {etat: float(mn.emergence)} result =None# TODO etudiantreturn result# Test (decommenter une fois implemente) :# em = emergence_by_state(pyphi.examples.macro_network())# print(sorted(em.items(), key=lambda kv: -kv[1])[:3])print("Exercice 2 a completer")
Exercice 2 a completer
Exercice 3 — Coarse-graining vs blackboxing
PyPhi propose deux stratégies de réduction d’échelle : le coarse-graining (regrouper des nœuds en macro-nœuds) et le blackboxing (cacher la dynamique interne d’un module, n’exposer que ses entrées/sorties à un pas de temps plus lent).
Implémentez compare_strategies(network, state) qui renvoie un dict {"coarse": émergence_coarse, "blackbox": émergence_blackbox, "both": émergence_both} en variant les arguments do_coarse_grain / do_blackbox de M.emergence.
Indice : trois appels à M.emergence avec les combinaisons d’arguments booléens.
Attention : le blackboxing peut échouer sur certains réseaux (capturer l’exception et renvoyer None pour cette stratégie).
def compare_strategies(network, state):# Etape 1 : coarse = M.emergence(network, state, do_coarse_grain=True, do_blackbox=False).emergence# Etape 2 : blackbox= M.emergence(network, state, do_coarse_grain=False, do_blackbox=True).emergence (try/except)# Etape 3 : both = M.emergence(network, state, do_coarse_grain=True, do_blackbox=True).emergence (try/except) result =None# TODO etudiantreturn result# Test (decommenter une fois implemente) :# print(compare_strategies(pyphi.examples.macro_network(), (0, 0, 0, 0)))print("Exercice 3 a completer")
Exercice 3 a completer
Conclusion
L’émergence causale renverse une intuition réductionniste : la description la plus fine n’est pas toujours la plus causale. Avec le module pyphi.macro (le vrai outil SOTA), nous avons :
mesuré \(\Phi\) et l’information effective à l’échelle micro ;
cherché automatiquement l’échelle macro qui maximise \(\Phi\) ;
montré une émergence franche (+0,48) sur le système canonique de Hoel, et son absence (émergence négative) sur des systèmes déjà micro-intégrés (XOR, basic_network).
L’outil est discriminant : il sépare les systèmes qui émergent de ceux qui n’émergent pas — ce qu’un cas dégénéré (où tout « émerge ») ne permettrait jamais.
Limite. La recherche énumère toutes les agrégations : son coût croît très vite avec le nombre de micro-nœuds (cloisonnement combinatoire de \(\Phi\)). On reste ici à \(\le 4\) nœuds. Pour des systèmes plus grands, il faut des heuristiques de partitionnement — un fil que prolonge la littérature multi-échelle.
ICT-0 — Cadrage : place de ce notebook dans la feuille de route.
Pour aller plus loin
Notebook-pont — du graphe causal au do-calculus : l’armature formelle unifiée de Pearl (échelle + 3 règles du do-calculus, paradigme interventionniste) exécutée sur dowhy, à mettre en regard de l’émergence causale de Hoel (information effective) de ce notebook.
Hoel, E. (2017). When the map is better than the territory. Entropy.
Jansma, A. & Hoel, E. (2025). Engineering Emergence. arXiv.