Visualize results#

PyPhi ships plotting helpers for the objects it computes: the cause-effect structure and its relations, the connectivity and transition probabilities of a substrate, cause and effect repertoires, a simulated trajectory, and the Ising building blocks used to generate substrates. This guide walks through each on a small example system.

The cause-effect-structure plots (plot_ces, highlight_phi_fold) use Plotly and are interactive — drag to rotate the 3-D hypergraph, hover for detail. The rest use Matplotlib. Rendering the interactive figures in a page like this one needs the connected Plotly renderer selected once:

import warnings

warnings.filterwarnings("ignore")

import numpy as np
import matplotlib.pyplot as plt
import plotly.io as pio

pio.renderers.default = "notebook_connected"

import pyphi
from pyphi import examples, visualize as viz

pyphi.config.progress_bars = False

Plotting a cause-effect structure#

plot_ces renders the cause-effect structure of a system — its distinctions and the relations among them. Compute one first:

ces = examples.xor_system().ces()

(The XOR system’s system-level φₛ is 0 — see The intrinsic-information requirement — but its cause-effect structure is rich, which is what the plots show.)

plot_ces offers five views of the same structure, chosen with view. The lattice view is a 2-D Hasse diagram of the distinctions ordered by inclusion; marker size encodes each distinction’s total relation φ (\(\Sigma\varphi_r\)), color its own φ.

viz.plot_ces(ces, view="lattice")

The hypergraph view places each distinction’s cause and effect purviews as vertices in 3-D and draws the relation faces among them:

viz.plot_ces(ces, view="hypergraph")

The scatter view embeds distinctions on a deterministic projection of their unit composition, sized by relation φ and colored by relational role:

viz.plot_ces(ces, view="scatter")

The matrix view is a distinction-by-distinction heatmap of shared relation φ, with each distinction’s self-relation strength on the diagonal:

viz.plot_ces(ces, view="matrix")

The spectrum view summarizes the high-degree structure as a bar panel of relation count and \(\Sigma\varphi_r\) per relation degree:

viz.plot_ces(ces, view="spectrum")

Visualizing analytically-computed relations#

For larger structures the relation set is far too big to enumerate, so by default PyPhi computes relations analytically — every aggregate answered in closed form, nothing materialized (see Query relational structure). Because that relation set cannot be listed, plot_ces renders the strongest relations by \(\varphi_r\), defaulting to the strongest 1000 when the set is not enumerable; max_relations chooses the cap:

analytical_ces = examples.xor_system().ces()

viz.plot_ces(analytical_ces, view="lattice", max_relations=8)

The cap limits only how many relation edges are drawn. Marker size still reflects each distinction’s true total relation φ over the whole structure, computed in closed form, so node sizes do not change as the cap tightens:

from pyphi.visualize.projection import project_ces

tight = [n.sum_phi_relations for n in project_ces(analytical_ces, max_relations=1).nodes]
loose = [n.sum_phi_relations for n in project_ces(analytical_ces, max_relations=99).nodes]
print("identical node sizes:", tight == loose)
print(tight)
identical node sizes: True
[3.666666666666667, 3.666666666666667, 3.666666666666667, 4.5]

The spectrum view likewise shows the exact per-degree census regardless of the cap, since it reads the closed-form degree spectrum rather than the drawn edges:

viz.plot_ces(analytical_ces, view="spectrum", max_relations=1)

Plotting a structure you built yourself#

A structure you assembled by hand — for instance, distinctions restored from a checkpoint — plots the same way. Build a CauseEffectStructure; the plot path never reads its sia, so None is fine, and the only requirement is that the structure be relation-closed:

from pyphi.models import CauseEffectStructure

rebuilt = CauseEffectStructure(None, ces.distinctions, ces.relations)
viz.plot_ces(rebuilt, view="lattice")

Highlighting a Φ-fold#

A Φ-fold is a seed distinction together with every relation incident to it. highlight_phi_fold draws the whole structure dimmed with the fold in full color, so a distinction’s relational neighborhood stands out. Seed a fold with a mechanism drawn from the structure:

fold = ces.fold([(0, 1)])
viz.highlight_phi_fold(fold)

Substrate connectivity#

plot_system draws a substrate’s connectivity graph, coloring each node by whether it belongs to the subsystem and by its current state:

system = examples.xor_system()
viz.plot_system(system);
../_images/5831a24f551777e83925b32b6c778772e39c050f3dbaf57ac19eb4dd6e23fa62.png

plot_graph draws the bare directed graph of any connectivity matrix (as a networkx graph), without the system coloring:

import networkx as nx

graph = nx.from_numpy_array(system.cm, create_using=nx.DiGraph)
viz.plot_graph(graph)
../_images/72d7d40a6d7ff661a037e3af4e1d2261eb812da0ff0bceb1a1210979c329ce9a.png

plot_tpm shows the transition probability matrix as a heatmap. It expects a state-by-state matrix (both dimensions a power of two), so convert from the factored state-by-node form first:

from pyphi import convert

state_by_node = examples.xor_substrate().tpm.to_pandas().values
state_by_state = convert.state_by_node2state_by_state(state_by_node)
fig, ax = viz.plot_tpm(state_by_state)
../_images/107c5a2ccccc6122dbf004e1e1276f6d428107aa255b25f3ce9eed5d4e3b87ba.png

Repertoires#

A repertoire is a probability distribution over states. plot_distribution draws one (or several, overlaid) as a bar panel — here the system’s cause repertoire over its past states:

repertoire = system.cause_repertoire(system.node_indices, system.node_indices)
fig, ax = viz.plot_distribution(repertoire)
../_images/c7c80d3644bc4397c009ade9101d11151d805f75c10241566b889e6d18844308.png

plot_repertoires compares the intact system’s forward repertoire against its minimum-information-partitioned counterpart, in both directions, from a system-irreducibility analysis:

from pyphi.formalism import queries

sia = queries.sia(system)
fig, axes, reps = viz.plot_repertoires(system, sia)
../_images/5ae1329ff8696e2b3b5638f31e8976b418c63e899d2d7c0f113ff40c50501bc9.png

Trajectories#

plot_dynamics draws a state raster: time on the x-axis, units on the y-axis, cell brightness for each unit’s state along a trajectory. Simulate one from a multidimensional state-by-node TPM (with a seeded generator, so it reproduces):

from pyphi import dynamics

tpm = convert.to_multidimensional(examples.xor_substrate().tpm.to_pandas().values)
trajectory = dynamics.simulate(
    tpm, initial_state=(1, 0, 0), timesteps=20, rng=np.random.default_rng(0)
)
fig, ax = viz.plot_dynamics(np.array(trajectory), node_labels=list(system.node_labels))
../_images/284b0b1e4b2dca2ef9b8c7cac06efdbcca02088d5e725d4038f411a1339bf189.png

Any array of shape (timesteps, units) works, so a recorded or synthetic trajectory can be drawn the same way.

Ising building blocks#

The ising helpers visualize the activation function used by the Ising substrate generator. plot overlays the activation sigmoid with each state’s input energy against its spin-on probability, for a given coupling matrix, temperature, and field:

weights = np.array([[0.0, 1.0], [1.0, 0.0]])
viz.ising.plot(weights, temperature=1.0, field=0.0);
../_images/006cf4fac2c7254cb891186a705c9d63acf6235b86d4f3130de0d0b5972dae11.png

plot_sigmoid draws the activation probability curve alone, over a range of input energies:

viz.ising.plot_sigmoid(np.linspace(-5, 5, 100), temperature=1.0, field=0.0);
../_images/55ed72b50ec965c7072b6e12931bc864961fae271406ebba0e33f46fad1f6497.png

Saving a figure#

The cause-effect-structure plots (plot_ces, highlight_phi_fold) are Plotly figures. Save one as a self-contained HTML file — no network or CDN, opens in any browser — with inline Plotly.js:

fig = viz.plot_ces(ces, view="hypergraph")
fig.write_html("ces.html", include_plotlyjs="inline")

Do not rasterize these to PNG: static image export needs kaleido, which PyPhi does not depend on, and these figures are meant to be rotated and hovered. Keep the cause-effect-structure plots as HTML. The Matplotlib figures (plot_system, plot_graph, plot_tpm, plot_distribution, plot_repertoires, plot_dynamics, the Ising helpers) save as images the usual way:

fig.savefig("figure.png", dpi=120, bbox_inches="tight")