AI Assistant Guide for PyPhi Development
This document provides context and guidelines for AI assistants working on PyPhi, a Python library that implements the mathematical formalism of Integrated Information Theory (IIT).
- Project Overview
- Critical Context
- Architecture & Organization
- Development Guidelines
- Testing Strategy
- Configuration System
- Common Pitfalls
- Maintenance Notes
PyPhi is a computational implementation of Integrated Information Theory (IIT), a mathematical framework for understanding consciousness and integrated information in physical systems. The library computes Φ (phi), the measure of integrated information, along with related quantities.
- Domain: Neuroscience, consciousness studies, complex systems
- Primary users: Researchers, academics, computational neuroscientists
- Computational characteristics: Heavy numerical computation, combinatorially expensive operations
- Theory versions: Supports both IIT 3.0 and IIT 4.0 (currently on 4.0)
-
IIT 4.0 Theory (2023):
Albantakis L, Barbosa L, Findlay G, Grasso M, ... Tononi G. (2023) Integrated information theory (IIT) 4.0: formulating the properties of phenomenal existence in physical terms. PLoS Computational Biology 19(10): e1011465. https://doi.org/10.1371/journal.pcbi.1011465 -
PyPhi Software (2018):
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
Additional key theoretical papers are in @papers.
- Documentation: https://pyphi.readthedocs.io
- Repository: https://github.com/wmayner/pyphi
- User group: https://groups.google.com/forum/#!forum/pyphi-users
- Tutorial: IIT 4.0 demo notebook in
docs/examples/IIT_4.0_demo.ipynb
This is scientific software implementing a precise mathematical formalism.
- Small bugs can invalidate research results
- Numerical precision matters deeply (configured via
PRECISIONsetting) - Changes to core computation logic require extreme care
- When in doubt, consult the IIT papers and existing tests
When an audit or investigation produces a "probably no effect, worth confirming later" claim, run the confirmation experiment as part of the audit. Locking state (goldens, fixtures, baselines, snapshot tests) onto an unconfirmed assumption multiplies the revalidation cost when the assumption turns out wrong — and once the state is committed, downstream work that builds on it inherits the assumption silently.
Motivating case: an IIT 3.0 tie-resolution audit deferred a five-minute confirmation experiment based on the structural assumption "the path has a unique MIP by construction". The assumption was false; four goldens were locked to buggy values for six days before the deferred experiment finally ran in a downstream investigation and exposed the bug.
Approach changes conservatively:
- Read existing code thoroughly before modifying
- Prioritize refactoring and testing over new features
- Don't assume the current implementation is optimal
- Look for inconsistencies and opportunities to improve clarity
The library is built around these primary objects:
-
Network(pyphi/network.py)- Represents a system of nodes with causal relationships
- Defined by a Transition Probability Matrix (TPM) and connectivity matrix
- Main object on which computations are performed
-
Subsystem(pyphi/subsystem.py)- A subset of nodes from a Network in a particular state
- Φ is computed over subsystems
- Handles repertoire computation, mechanism evaluation
-
TPM (Transition Probability Matrix) (
pyphi/tpm.py)- Core data structure defining system dynamics
- Can be deterministic or probabilistic
- Multiple representations: state-by-node, state-by-state
pyphi/
├── __init__.py # Main entry point, lifts key interfaces
├── compute/ # Main computational entry points
│ ├── network.py # Network-level computations
│ └── subsystem.py # Subsystem-level computations
├── models/ # Data structures for results
│ ├── subsystem.py # CauseEffectStructure, Concept, etc.
│ ├── mechanism.py # RepertoireIrreducibilityAnalysis
│ ├── cuts.py # Partition/cut representations
│ └── ...
├── metrics/ # Distance measures for integration
│ ├── ces.py # Cause-effect structure distances
│ └── distribution.py # Repertoire distance measures
├── new_big_phi/ # IIT 4.0 implementation
│ └── __init__.py # System-level analysis (Φ_s)
├── partition.py # Partitioning schemes
├── repertoire.py # Repertoire computation
├── parallel/ # Parallelization infrastructure
│ ├── tree.py # Parallel tree computation
│ └── progress.py # Progress bar management
├── cache/ # Caching systems
│ ├── redis.py # Redis cache backend
│ └── cache_utils.py # Cache utilities
├── network_generator/ # Generate example networks
├── visualize/ # Visualization tools (optional dep)
└── ...
The library supports both IIT 3.0 and IIT 4.0:
- Config setting:
IIT_VERSION: 4.0inpyphi_config.yml - IIT 4.0 code: Primarily in
pyphi/new_big_phi/ - IIT 3.0 code: Distributed throughout the codebase (legacy)
The formalism differences are significant:
- IIT 3.0: Focuses on cause-effect structure (Φ)
- IIT 4.0: Adds system-level integration (Φ_s), relations, distinctions
-
Φ (Big Phi): Integrated information of a system
- Computed by finding the Minimum Information Partition (MIP)
- Combinatorially expensive: requires evaluating all partitions
-
φ (Small Phi): Mechanism integration
- How irreducible a mechanism's cause-effect repertoire is
-
Repertoires: Probability distributions over states
- Cause repertoire: Past states that could lead to current state
- Effect repertoire: Future states the system could transition to
-
Partitions/Cuts: Ways of disconnecting a system
- Used to test irreducibility
- Different partition schemes available
-
Distinctions: Irreducible mechanisms (IIT 4.0)
- Concepts with cause-effect power
-
Relations: Dependencies between distinctions (IIT 4.0)
-
Read the relevant code first
- Use Read to understand current implementation
- Check tests for expected behavior
- Consult IIT papers for theoretical grounding
-
Understand the mathematics
- Don't change computation logic without understanding the theory
- If unsure, ask the user or consult documentation
-
Check configuration
- Many behaviors are configurable
- See pyphi_config.yml and pyphi/conf.py
-
Type Hints
- Add type hints to new code
- Gradually add to existing code when touching it
- Use
Optional,Tuple,Iterableappropriately
-
Documentation — docstring style (enforced)
All
pyphi/**docstrings follow one standard, enforced by the docs build (napoleon_google_docstring = False, so a Google-style section fails the-Wbuild). New and modified docstrings must match it:- NumPy style, not Google. Underlined sections —
Parameters,Returns,Yields,Raises, and, where they add value,NotesandReferences(neverArgs:/Returns:colon headers). A param isname : typewith the description indented beneath. - Final-state, impersonal voice. Describe what the thing is and does, in the present tense. Never what it was, replaced, or how it was built; no first person, no development-process or planning narrative. The test: would the sentence still make sense to someone who uses PyPhi for years and never sees its git history?
- Substantive insight is welcome. A mathematical fact, a complexity
bound, a numerical-stability caveat, or a subtle usage requirement belongs
in
Notes(NumPy-docstring spirit). The rule bans process narrative, not subject-matter explanation. Anything asserted must be verifiable against the code or a cited source. - Plain, precise prose. Prefer plain words where they are equally exact; never sacrifice precision for simplicity. Avoid compressed shorthand (quoted-phrase adjectives, hyphen-chain nouns, stacked modifiers).
- Symbols: Unicode, not RST substitutions. Write
Φ,φ,φₛ,𝒜,α,×,−directly (they read correctly underhelp()); use Unicode sub/superscripts where they exist. These are whitelisted in ruff'sallowed-confusables. Reserve the:math:role for genuine multi-part formulae (fractions, sums) that Unicode cannot express. Do not reintroduce|big_phi|-style substitution markup — there is norst_prologdefining it. - Cite the literature where a docstring documents a paper result — the
exact equation/section/figure number, verified against the actual paper
(
papers/) — in aReferencessection ([1]_entries) or inline short-form ("Albantakis et al. (2023), Eq. 33"). Never cite a number from memory.graphify-out/bridge-edges.jsonmaps code files to paper concepts. - Doctests are executable and tested (
--doctest-modules); keep>>>lines and their output correct.
- NumPy style, not Google. Underlined sections —
-
Testing
- Write tests for all new functionality
- Use property-based testing (Hypothesis) for mathematical properties
- Example networks are in
test/example_networks.py
-
Performance
- This code is computationally expensive by nature
- Profile before optimizing
- Consider caching strategies
- Parallelization is available via Ray (optional dependency)
-
Changelog Fragments
- When making user-facing changes, create a changelog fragment in
changelog.d/ - Fragment filename format:
<name>.<type>.mdwhere:<name>is a GitHub issue number (e.g.,123) or descriptive name (e.g.,fix-cache-bug)<type>is one of:feature,change,config,optimization,fix,doc,refactor,misc
- Example:
echo "Added \new_function()`" > changelog.d/new-function.feature.md` - Use
uv run towncrier create <name>.<type>.mdfor guided creation - See
changelog.d/README.mdfor full documentation
- When making user-facing changes, create a changelog fragment in
Always use uv run for running any python development commands (for example,
uv run python). Use uv pip when pip is needed.
# Install uv (if not already installed)
curl -LsSf https://astral.sh/uv/install.sh | sh
# Create virtual environment
uv venv
# Install development dependencies
uv pip install -e ".[dev,parallel,visualize,graphs,emd,caching]"
# Run type checking
uv run pyright pyphi
# Run benchmarks (quick, current env)
just bench
# Build documentation
just docs
# Check configuration
uv run python -c "import pyphi; print(pyphi.config)"- Formatting & Linting: Project uses Ruff (configured in
pyproject.toml)- Enabled rule sets: pycodestyle, pyflakes, isort, bugbear, comprehensions, pyupgrade, return statements, unused arguments, pathlib, pylint, performance, and Ruff-specific rules
- Special allowances: Relaxed limits for scientific computing (complex functions, many arguments, magic values)
- Per-file ignores: Tests allow fixtures and assertions, profiling allows print statements
- Do not run unsafe fixes with Ruff without first getting permission from the user.
- Type Checking: pyright (configured in
pyproject.toml)- Uses "standard" type checking mode
- Better numpy type inference than mypy
- Faster and more accurate for scientific Python code
- Pre-commit hooks: Configured in
.pre-commit-config.yaml- Ruff linter and formatter
- pyright type checker
- Standard file checks (trailing whitespace, large files, etc.)
We will support only Python 3.13+ for this version. Therefore, when writing code, do not attempt to maintain backward compatibility with previous Python versions.
pyphi/ # Doctests
docs/ # Doctests
test/ # Unit, regression, and e2e tests
├── conftest.py # Pytest configuration and fixtures
├── example_networks.py # Reusable network definitions
├── test_*.py # ~460 test functions across ~30 files
└── data/ # Test data (JSON, etc.)
├── PQR_CES.json
└── ...
Note: Test configuration is now in pyproject.toml under [tool.pytest.ini_options].
- Unit Tests: Test individual functions and methods
- Integration Tests: Test complete computations (e.g., full Φ calculation)
- Property-Based Tests: Use Hypothesis for invariant testing
- Regression Tests: Ensure results match expected values from papers
- Doctests: Ensure documentation is current and documented behavior is as expected
Use the pre-defined networks in pyphi.examples:
from pyphi import examples
# Standard test networks
network = examples.basic_network()
network = examples.xor_network()
network = examples.fig1a() # From IIT 3.0 paper# All tests, including doctests
uv run pytest
# Test suite
uv run pytest test/
# Specific test file
uv run pytest test/test_subsystem.py
# Specific test function
uv run pytest test/test_subsystem.py::test_cause_repertoire
# With coverage
uv run coverage run --source pyphi -m pytest
uv run coverage html
# Run the test suite (forwards args to pytest)
just testNote: Coverage configuration is now in pyproject.toml under [tool.coverage.*].
The pytest config in pyproject.toml sets testpaths = ["pyphi", "test"] and addopts = ["--doctest-modules", "--doctest-glob=*.rst", ...]. CI runs uv run pytest with no path argument, which uses
testpaths and collects doctests in pyphi/ source modules. Bare-path
invocations (pytest test/, pytest pyphi/specific.py) override
testpaths and skip the doctest sweep entirely — local verifications
scoped this way will report green even when a doctest is broken.
When verifying a project as complete (especially renames, signature
changes, or anything touching pyphi/ source), run uv run pytest
without a path argument at least once. The fast-lane shortcut
(pytest test/ -m "not slow") is fine for inner-loop iteration but is
not a complete verification recipe.
Doctests don't run on docs/*.rst files either, because docs/ isn't
in testpaths even though --doctest-glob=*.rst would match. Treat
docs/*.rst doctests as documentation that users can copy — verify by
reading, not by pytest.
A φ value is only meaningful relative to a formalism. Any test that asserts a
φ value must pin its formalism explicitly — never rely on the ambient
default. Pin with the complete preset-sourced context managers
(IIT_3_CONFIG, IIT_4_CONFIG in test/conftest.py, sourced from
pyphi.conf.presets), not a hand-listed subset of iit.* fields: setting
iit.version alone leaves the measures on the ambient default — the
partial-pin trap that silently recomputes under a different formalism when the
default changes. Tests that compute φ at module-fixture setup must pin inside
the fixture (a function-scoped autouse pin does not wrap module-fixture setup).
Exactly one test — test_default_formalism_is_iit4_2026 — asserts the shipping
default; it is intentionally unpinned. To flip the default formalism: change
the default in pyphi/conf/formalism.py, update that assertion plus the two
default-dependent facade tests (TestGlobalConfigFacade.test_layered_reads_work
in test/conf/test_config_layers.py and test_2023_omitted_metric_uses_default
in test/formalism/test_formalism_measure_threading.py), and regenerate only
the docs/ tutorial examples that demonstrate default behavior (CI doctests in
pyphi/ compute no cap-sensitive φ).
The full suite takes a while. For faster signal, split into independent test files and run them as parallel background jobs rather than sequentially in one command:
- Fast lane (seconds-to-minute):
test_partition.py,test_subsystem_surface.py,test_golden_regression.py,test_invariants.py(deterministic invariants, no Hypothesis) - Slow lane (5-10 min):
test_invariants_hypothesis.py(property tests with@settings(max_examples=...))
Pattern: kick off the slow lane in background with
run_in_background=true, then run the fast lane in foreground. You'll
see the fast results in <1 min while the slow lane keeps running, and
get notified when the slow lane finishes via Monitor's until loop.
Don't bundle slow + fast into a single pytest invocation — pytest's
sequential collection means the fast result is gated on the slow one.
Exit codes and pipes — read the summary, not the exit code. Never pipe a
test run through tail/head/grep when the result matters: the pipeline's
exit code is the last command's, so pytest … | tail -3 reports success
even when pytest fails or errors out. Redirect to a file instead
(uv run pytest -q > log 2>&1, exit code stays pytest's own), then read the
file's summary line before claiming green. This exact trap has shipped a
regression: two "exit 0" background runs each contained a real test failure
that went unread. Related: the slow lane is uv run pytest -m slow --slow —
the root conftest errors loudly if --slow is missing, so a bare -m slow
can never silently skip-and-pass, but only if that error is actually seen.
- Default configuration: Defined as frozen dataclasses in
pyphi/conf/—formalism.py(IITConfig,ActualCausationConfig),infrastructure.py(InfrastructureConfig),numerics.py(NumericsConfig). - User configuration: Loaded from
pyphi_config.ymlin working directory (nested format: top-level keysformalism/infrastructure/numerics). - Runtime changes:
pyphi.config.option_name = value(top-level write routes to the right layer) orpyphi.config.numerics.override(...). - Context managers:
pyphi.config.override(...)for temporary scopes.
Example:
import pyphi
# Check current value
print(pyphi.config.numerics.precision) # 13
# Change at runtime
pyphi.config.precision = 6
# Temporary change
with pyphi.config.override(precision=10):
# Computation with higher precision
passSee pyphi/conf/CLAUDE.md for the full option-by-option reference (loads automatically when working in that directory).
From TODO.md and codebase inspection:
-
Type Hints Incomplete
- Many functions lack type annotations
- Gradually add when touching code
-
API Documentation Outdated
- Needs regeneration after recent reorganization
- Use
apidocor similar tool
-
Redis Cache Underutilized
- Infrastructure exists but may not be fully leveraged
- Consider for distributed computation scenarios
-
Unified Partitioning Scheme Needed
- Multiple partition types, could be more consistent
- See
pyphi/partition.py
-
IIT 3.0 Module Separation
- IIT 3.0 code should be isolated for clarity
- Currently mixed throughout codebase
Untracked files to ignore:
test-iit4.ipynb,visualize-example.ipynb- Experimental notebookstest/test_parallel2.py,test/test_serialization.py- Experimental tests
Strengths:
- Clear separation of concerns (compute, models, metrics)
- Registry pattern for extensibility
- Comprehensive configuration system
- Good test coverage of core functionality
Areas for Improvement:
- Some modules are quite large (
subsystem.py~1,900 lines,conf.py~1,000 lines) - IIT 3.0 vs 4.0 code not clearly separated (except
new_big_phi/) - Circular import issues managed with deferred imports
- Some inconsistency in naming conventions
When improving the codebase, prioritize:
- Add type hints - Improves IDE support and catches bugs
- Improve test coverage - Especially edge cases
- Separate IIT versions - Make version differences explicit
- Document complex algorithms - Especially partition evaluation
- Reduce code duplication - Look for repeated patterns
- Performance profiling - Identify bottlenecks before optimizing
- Increase coverage of edge cases
- Property-based tests for mathematical invariants
- Performance regression tests via benchmarking
- Integration tests for IIT 4.0 (newer code path)
- Parallel computation tests (may require special setup)
Do:
- Ask for explanations of unfamiliar IIT concepts
- Request code review before submitting changes
- Ask for help writing property-based tests
- Request refactoring suggestions with justification
Don't:
- Make changes to core computation logic without understanding
- Assume existing code is bug-free (it needs maintenance!)
- Skip testing because "it's just a small change"
- Ignore numerical precision requirements
- Read relevant existing code
- Write tests first (TDD)
- Implement feature
- Run tests and fix failures
- Add documentation
- Consider the MCP server (
pyphi/mcp/): decide whether the new information or functionality should be surfaced there — as a tool inserver.py, a resource inresources.py, or reference content inpyphi/mcp/content/— and update those surfaces if so - Create changelog fragment in
changelog.d/ - Request code review
- Write a failing test that reproduces the bug
- Investigate root cause
- Fix the issue
- Verify test passes
- Check for similar bugs elsewhere
- Add regression test
- Create changelog fragment in
changelog.d/
- Ensure tests exist for current behavior
- Make incremental changes
- Run tests after each change
- Verify performance hasn't regressed
- Update documentation if API changes
Commit messages must succinctly describe what changed and why. Do not include anything related to the narrative flow of conversations with the user, or context that is irrelevant to the actual final set of changes. BAD: "User flagged an important issue. This commit fixes…". GOOD: "This commit fixes a bug where…".
Design specs and implementation plans (e.g. under docs/superpowers/) must
only be committed after the user has explicitly approved them. Do not
commit a spec or plan in the same breath as writing it — write it, ask the
user to review, and commit only once they sign off. The same applies to
substantive revisions of an already-approved spec/plan: re-confirm before
committing the revision.
The default is to work on whatever branch the conversation starts on. However, for significant chunks of work that require discussion and planning, you should prefer working in a git worktree (after confirming with the user).
Create worktrees in .claude/worktrees/.
- ROADMAP.md - Strategic 2.0 roadmap and schedule. The single source of truth for what has landed and what remains; the Status Dashboard at the top is authoritative. Read it for current priorities, and keep it current (see "Keeping this file up to date" below).
- pyphi/init.py - Main entry point
- pyphi/system.py, pyphi/substrate.py - Core
System/Substratevalue types (formerlySubsystem/Network) - pyphi/formalism/ - Formalism strategies:
iit3/,iit4/,actual_causation/ - pyphi/core/ - Stateless kernel: repertoire algebra, TPM (
core/tpm/), units - pyphi/conf/ - Layered configuration (formalism / infrastructure / numerics)
- pyphi_config.yml - Default configuration
- test/example_networks.py - Test networks
# Development setup
uv venv # Create virtual environment
uv pip install -e ".[dev,parallel,visualize]" # Install with dev dependencies
# Testing
just test # Run tests (forwards args)
uv run pytest # All tests
uv run pytest -k test_name # Specific test
uv run pytest --cov=pyphi # With coverage
# Benchmarking
just bench # Quick local run (current env)
just bench-dashboard # Build + serve the ASV HTML dashboard
# Documentation
just docs
open docs/_build/html/index.html
# Code quality
pre-commit run --all-files- Documentation: https://pyphi.readthedocs.io
- Issues: https://github.com/wmayner/pyphi/issues
- Discussion: https://groups.google.com/forum/#!forum/pyphi-users
- IIT 4.0 Paper: https://doi.org/10.1371/journal.pcbi.1011465
- PyPhi Paper: https://doi.org/10.1371/journal.pcbi.1006343
As the codebase changes, make sure to update the contents of this file as necessary.
ROADMAP.md is the strategic roadmap for the 2.0 release — the planned
refactors and features, their dependency-ordered schedule, and their status. When you land or
change the status of any roadmapped work, update its row in the ROADMAP.md Status Dashboard in the
same change (and any matching detail in "Remaining 2.0 Work"). The document has repeatedly drifted
— items implemented but left described as upcoming — so the dashboard is the single source of truth;
verify an item's status against the code, changelog.d/, and git history before trusting prose
elsewhere in the file. If you do substantial work that isn't on the roadmap, add it.
A commit that settles a gate — a confirmation experiment, a proof, or a refutation — must
update the gated item's ROADMAP row in the same change. A verdict that lives only in a commit
message or a FINDINGS.md will not be found by the next person reading the dashboard, and the
roadmap will keep describing the item as blocked or open long after the blocker is gone. This has
already happened: three theory gates were settled experimentally but left the dashboard describing
them as still-gated. When an experiment discharges (or kills) a gate, propagate the outcome to the
row, and to any wishlist entry or PAPER-IDEAS.md idea that cited the open question.
PyPhi implements a complex mathematical theory with real-world scientific applications. Changes to this codebase can affect research results. Approach all modifications with care, test thoroughly, and when in doubt, consult the theoretical papers and existing tests.
The project needs maintenance and refactoring work, which presents an opportunity to improve code quality while preserving mathematical correctness. Incremental improvements with comprehensive testing are the best approach.
Remember: This is scientific software. Correctness > performance > elegance.
This project can build a knowledge graph under graphify-out/ (god nodes, community structure, cross-file relationships) plus hand-built edges linking IIT paper concepts to the code that implements them (implements/cites), which answer "which function implements Theorem 1 / the intrinsic-difference measure / a given equation". Only the curated bridge edges are committed — graphify-out/bridge-edges.json (238 implements/cites edges + their endpoint nodes). The full graph.json and GRAPH_REPORT.md are large and regenerable, so they are local-only (gitignored) along with the rest of graphify-out/. Build them with graphify update ., then restore the committed bridge edges with uv run python scripts/graphify_bridge.py inject.
graphify is a standalone CLI, not a pyphi import dependency, so it is registered in the [dependency-groups] dev list (package name graphifyy, double-y; command is graphify). It installs with the rest of the dev tooling via uv sync, or on its own with uv tool install 'graphifyy==0.8.44'.
When to use it (optional — graphify is a convenience, not a required first step; it needs a local graph.json, which a fresh clone won't have until you run graphify update .):
- It earns its keep for paper-to-code traceability ("what implements concept X?", via
graphify path "<concept>" "<symbol>"/graphify explain "<concept>") — the bridge edges answer questions grep cannot — and for broad orientation across many files in code you don't already know. - For targeted lookups in code you can already navigate, plain grep/Read are usually faster and more precise than a graph query (the bare
querydoes a broad keyword sweep and can return a large, noisy neighborhood). - Read graphify-out/GRAPH_REPORT.md (when present locally) only for broad architecture review.
Keeping it current:
graph.json/GRAPH_REPORT.mdare local. Rungraphify update .to refresh the structural (AST) layer (cheap, deterministic, no API cost), thenuv run python scripts/graphify_bridge.py injectto merge the committed bridge edges back into the freshly built graph.- The committed
graphify-out/bridge-edges.jsonis the version-controlled asset. The bridge edges do NOT refresh withgraphify update; rebuild them deliberately (a focused multi-agent pass reading the IIT papers alongside their implementing modules, emittingimplements/citesedges) after a release or before onboarding, then runuv run python scripts/graphify_bridge.py extractto refresh the committed sidecar (review the diff like a golden).