Sweep states and subsystems#
pyphi.sweep runs one IIT computation across many states and candidate
subsystems in a single call, and collects every result into
one tidy long-format DataFrame. It saves you from writing the nested loops,
building each System by hand, and stitching the results back together.
(To vary the substrate’s parameters — its connection weights — rather than
its state or the candidate system, see
Explore substrate parameter landscapes.)
import pyphi
pyphi.config.progress_bars = False
A first sweep#
sweep takes a substrate and at least one axis to vary. The states
argument is required. Pass "all" to enumerate every state of the substrate:
substrate = pyphi.examples.iit4_2023_fig1a_substrate()
result = pyphi.sweep(substrate, states="all")
result.df.round(6)
| phi | normalized_phi | is_irreducible | partition_margin | cause_state_margin | effect_state_margin | effectively_tied | substrate | formalism | subset | |
|---|---|---|---|---|---|---|---|---|---|---|
| state | ||||||||||
| (0, 0, 0) | 0.067358 | 0.033679 | True | 0.044650 | 1.182755 | 0.903435 | False | 0 | IIT_4_0_2026 | (0, 1, 2) |
| (1, 0, 0) | 0.133873 | 0.066937 | True | 0.026941 | 0.003492 | 0.030059 | False | 0 | IIT_4_0_2026 | (0, 1, 2) |
| (0, 1, 0) | 0.000000 | 0.000000 | False | NaN | 0.203006 | 0.335151 | False | 0 | IIT_4_0_2026 | (0, 1, 2) |
| (1, 1, 0) | 0.000000 | 0.000000 | False | NaN | 0.407902 | 0.137647 | False | 0 | IIT_4_0_2026 | (0, 1, 2) |
| (0, 0, 1) | 0.000000 | 0.000000 | False | NaN | 0.407902 | 0.137647 | False | 0 | IIT_4_0_2026 | (0, 1, 2) |
| (1, 0, 1) | 0.000000 | 0.000000 | False | NaN | 0.203006 | 0.335151 | False | 0 | IIT_4_0_2026 | (0, 1, 2) |
| (0, 1, 1) | 0.133873 | 0.066937 | True | 0.026941 | 0.003492 | 0.030059 | False | 0 | IIT_4_0_2026 | (0, 1, 2) |
| (1, 1, 1) | 0.067358 | 0.033679 | True | 0.044650 | 1.182755 | 0.903435 | False | 0 | IIT_4_0_2026 | (0, 1, 2) |
Each row is one system-level integrated information analysis (an
SIA). The index holds the axis that
varied — here, the state — and the columns hold the extracted quantities:
phi, normalized_phi, whether the system is irreducible, and the selection
margins (partition_margin, cause_state_margin, effect_state_margin,
effectively_tied — see
Control tie-breaking). The axes that did not vary
appear as constant context columns (formalism, subset).
States that cannot be reached from any previous state have no defined
repertoire, so their \(\Phi\) is undefined. When you enumerate an axis with
"all", those cells are dropped rather than raised, and the dropped cells
are recorded on the result. Our probabilistic substrate reaches every state,
so nothing was dropped here:
result.skipped
[]
With a deterministic substrate you can see this happen — two of the eight
states of the three-gate basic network are unreachable:
pyphi.sweep(pyphi.examples.basic_substrate(), states="all").skipped
[(0, 'IIT_4_0_2026', (0, 1, 2), (0, 1, 0)),
(0, 'IIT_4_0_2026', (0, 1, 2), (0, 1, 1))]
The result object#
sweep returns a SweepResult with three fields:
df— the tidy table (also available asresult.to_pandas()).results— the raw result objects, aligned one-to-one with the rows ofdf, so you can reach into any cell for detail that is not in the table.skipped— the(formalism, subset, state)cells dropped as unreachable.
type(result.results[0]).__name__
'SystemIrreducibilityAnalysis'
Sweeping over subsystems#
The subsets argument chooses which subsets of nodes to treat as the
candidate system. Pass "all" for the non-empty powerset, "full" (the
default) for the whole substrate, or an explicit list of node-index tuples.
Here we hold the state fixed and vary the subsystem:
pyphi.sweep(substrate, states=(0, 1, 1), subsets="all").df.round(6)
| phi | normalized_phi | is_irreducible | partition_margin | cause_state_margin | effect_state_margin | effectively_tied | substrate | formalism | state | |
|---|---|---|---|---|---|---|---|---|---|---|
| subset | ||||||||||
| (0,) | 0.038891 | 0.038891 | True | NaN | 0.077118 | 0.107132 | False | 0 | IIT_4_0_2026 | (0, 1, 1) |
| (1,) | 0.001606 | 0.001606 | True | NaN | 0.202298 | 0.004903 | False | 0 | IIT_4_0_2026 | (0, 1, 1) |
| (2,) | 0.212220 | 0.212220 | True | NaN | 0.374172 | 0.497867 | False | 0 | IIT_4_0_2026 | (0, 1, 1) |
| (0, 1) | 0.040497 | 0.040497 | True | 0.213401 | 0.760812 | 1.097995 | False | 0 | IIT_4_0_2026 | (0, 1, 1) |
| (0, 2) | 0.000000 | 0.000000 | False | NaN | NaN | NaN | False | 0 | IIT_4_0_2026 | (0, 1, 1) |
| (1, 2) | 0.000000 | 0.000000 | False | NaN | NaN | NaN | False | 0 | IIT_4_0_2026 | (0, 1, 1) |
| (0, 1, 2) | 0.133873 | 0.066937 | True | 0.026941 | 0.003492 | 0.030059 | False | 0 | IIT_4_0_2026 | (0, 1, 1) |
When more than one axis varies at once, the index becomes a MultiIndex with
one level per varying axis. The formalisms= argument adds one more axis, for
computing the same cells under earlier versions of IIT; see
Reproduce results from earlier versions of IIT.
Choosing what to compute#
By default each cell computes an SIA. Pass compute="ces" to compute the full
cause-effect structure instead. The extracted columns change to match: the
number of distinctions and the summed relation \(\varphi\).
pyphi.sweep(substrate, states="all", compute="ces").df.round(6)
| phi | n_distinctions | sum_phi_r | substrate | formalism | subset | |
|---|---|---|---|---|---|---|
| state | ||||||
| (0, 0, 0) | 0.067358 | 2 | 0.070801 | 0 | IIT_4_0_2026 | (0, 1, 2) |
| (1, 0, 0) | 0.133873 | 6 | 2.917676 | 0 | IIT_4_0_2026 | (0, 1, 2) |
| (0, 1, 0) | 0.000000 | 4 | 0.908204 | 0 | IIT_4_0_2026 | (0, 1, 2) |
| (1, 1, 0) | 0.000000 | 2 | 0.214175 | 0 | IIT_4_0_2026 | (0, 1, 2) |
| (0, 0, 1) | 0.000000 | 2 | 0.214175 | 0 | IIT_4_0_2026 | (0, 1, 2) |
| (1, 0, 1) | 0.000000 | 4 | 0.908204 | 0 | IIT_4_0_2026 | (0, 1, 2) |
| (0, 1, 1) | 0.133873 | 6 | 2.917676 | 0 | IIT_4_0_2026 | (0, 1, 2) |
| (1, 1, 1) | 0.067358 | 2 | 0.070801 | 0 | IIT_4_0_2026 | (0, 1, 2) |
compute also accepts any callable taking a System. The callable’s return
value is stored in result.results for every cell; reach into that list to
work with whatever it returns.
Find near-tied cells#
The margin columns make it a one-liner to find the cells whose selections
were effectively tied at the configured precision — the results whose
reported partitions or specified states are sensitive to tie-breaking rules
— none, for this asymmetric substrate:
tied = result.df[result.df.effectively_tied.astype(bool)]
tied[["phi", "partition_margin", "cause_state_margin", "effect_state_margin"]].round(6)
| phi | partition_margin | cause_state_margin | effect_state_margin | |
|---|---|---|---|---|
| state |
Cells computed under IIT 3.0, which does not report margins, have None in
these columns.
Running in parallel#
Set parallel=True to spread the cells across worker processes. Each cell is a
whole SIA or CES computation, so this parallelizes at the level of the sweep
rather than inside any single computation. Passing None (the default) follows
config.infrastructure.parallel.
pyphi.sweep(substrate, states="all", parallel=True).df.round(6)["phi"]
state
(0, 0, 0) 0.067358
(1, 0, 0) 0.133873
(0, 1, 0) 0.000000
(1, 1, 0) 0.000000
(0, 0, 1) 0.000000
(1, 0, 1) 0.000000
(0, 1, 1) 0.133873
(1, 1, 1) 0.067358
Name: phi, dtype: float64
The results are returned in the same order whether or not the sweep runs in parallel, so the table is identical either way.
Reproducibility#
Pass a seed to stamp it into every result’s provenance record, so a saved
sweep records the seed that produced it alongside the numbers:
seeded = pyphi.sweep(substrate, states="all", seed=42)
seeded.df.shape
(8, 10)