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);
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)
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)
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)
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)
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))
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);
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);
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")