Source code for pyphi.formalism.iit3.formalism

# pyright: strict
"""Concrete IIT 3.0 formalism class.

Delegates to the IIT 3.0 SIA algorithms in :mod:`pyphi.formalism.iit3`
(distribution-distance-based, bipartition-only).
"""

from __future__ import annotations

from dataclasses import dataclass
from typing import Any
from typing import ClassVar
from typing import Literal

from pyphi.conf import config
from pyphi.formalism.base import check_background_conditioning_compatible
from pyphi.formalism.base import check_measure_compatible
from pyphi.formalism.base import check_mechanism_partition_scheme_compatible
from pyphi.formalism.base import check_sia_tie_strategy_compatible


[docs] @dataclass(frozen=True) class IIT3Formalism: """IIT 3.0 (Oizumi et al. 2014) — distribution-distance phi computation.""" name: ClassVar[str] = "IIT_3_0" exact: ClassVar[Literal[True]] = True uses_system_phi_measure: ClassVar[bool] = False compatible_measures: ClassVar[frozenset[str]] = frozenset( { "EMD", "L1", "KLD", "ENTROPY_DIFFERENCE", "PSQ2", "MP2Q", "AID", "ID", } ) # IIT 3.0 derives system Φ from the CES distance, so ces_measure defines # its answer. EMD is the 2014 paper's measure; SUM_SMALL_PHI is the # small-phi-difference variant used by Gómez et al. (2020) for # multi-valued elements (where EMD is unavailable). compatible_ces_measures: ClassVar[frozenset[str]] = frozenset( {"EMD", "SUM_SMALL_PHI"} ) # IIT 3.0 MIP searches are plain minima with no specified-state pins. has_state_pins: ClassVar[bool] = False partition_scheme: ClassVar[str | None] = "JOINT_BIPARTITION" compatible_system_partition_schemes: ClassVar[frozenset[str] | None] = frozenset( {"DIRECTED_BIPARTITION", "DIRECTED_BIPARTITION_CUT_ONE"} ) compatible_mechanism_partition_schemes: ClassVar[frozenset[str] | None] = frozenset( {"JOINT_BIPARTITION", "WEDGE_TRIPARTITION"} ) # IIT 3.0 SIA results carry only raw phi and the MIP -- no normalized phi # or purview -- so SIA-level tie strategies reading those attributes have # nothing to read. compatible_sia_tie_strategies: ClassVar[frozenset[str] | None] = frozenset( {"PHI", "NEGATIVE_PHI", "PARTITION_LEX", "NONE"} ) # IIT 3.0 has no specified-state phase, so specification_measure is never # consulted. compatible_specification_measures: ClassVar[frozenset[str] | None] = None # The post-2014-literature convention fixes background units at their # observed current state on the cause side; IIT 4.0's causal # marginalization computes a different phi on proper-subset systems. compatible_background_conditioning: ClassVar[frozenset[str] | None] = frozenset( {"CONDITION_CURRENT_STATE"} )
[docs] def evaluate_mechanism( self, system: Any, direction: Any, mechanism: Any, purview: Any, **kwargs: Any, ) -> Any: """Public mechanism-level evaluation. Calls back through ``queries.find_mip`` to preserve the short-circuit logic (empty purview, unreachable state) the public dispatcher owns.""" from pyphi.formalism.queries import find_mip return find_mip(system, direction, mechanism, purview, **kwargs)
def _find_mechanism_mip( self, system: Any, direction: Any, mechanism: Any, purview: Any, repertoire: Any = None, partitions: Any = None, state: Any = None, parallel_kwargs: Any = None, **kwargs: Any, ) -> Any: """Internal mechanism-MIP search. Called by ``queries.find_mip`` after its short-circuit checks. Unlike IIT 4.0, IIT 3.0 has no candidate specified-state phase: a single search over the mechanism partitions minimizes φ for the fixed (mechanism, purview) pair. Passing ``state`` therefore raises :class:`ValueError`. Partitions that tie on minimum φ are resolved by :mod:`pyphi.resolve_ties`. """ from pyphi.formalism.queries import ( _find_mip_single_state, # pyright: ignore[reportPrivateUsage] ) check_measure_compatible(self, config.formalism.iit.mechanism_phi_measure) if partitions is None: # Partitions will be enumerated from the configured scheme. check_mechanism_partition_scheme_compatible( self, config.formalism.iit.mechanism_partition_scheme ) if state is not None: raise ValueError("passing `state` is not supported with IIT 3.0") return _find_mip_single_state( # pyright: ignore[reportPrivateUsage] system, None, direction, mechanism, purview, repertoire, partitions, parallel_kwargs, **kwargs, )
[docs] def evaluate_mechanism_partition( self, system: Any, direction: Any, mechanism: Any, purview: Any, partition: Any, repertoire: Any = None, partitioned_repertoire: Any = None, repertoire_distance: Any = None, partitioned_repertoire_kwargs: Any = None, **kwargs: Any, ) -> Any: """IIT 3.0 mechanism-partition integration: distribution-distance between unpartitioned and partitioned repertoires. ``repertoire_distance`` is a Protocol-typed measure callable (resolved here from config if not provided); ``mechanism_measure`` is threaded through to the partitioned-repertoire helper. """ from pyphi.measures.distribution import ( repertoire_distance as _repertoire_distance, # pyright: ignore[reportUnknownVariableType] ) from pyphi.measures.distribution import resolve_mechanism_measure from pyphi.models import RepertoireIrreducibilityAnalysis from pyphi.utils import state_of check_measure_compatible(self, config.formalism.iit.mechanism_phi_measure) if repertoire_distance is None: repertoire_distance = resolve_mechanism_measure( config.formalism.iit.mechanism_phi_measure, system.substrate.factored_tpm.alphabet_sizes, ) # Internal helpers below the formalism boundary require an # explicit ``mechanism_measure``; resolve it here. mechanism_measure = kwargs.pop("mechanism_measure", repertoire_distance) if repertoire is None: from pyphi.core import repertoire_algebra as ra # The kernel directly: these units are already sorted indices. repertoire = ra.repertoire(system, direction, mechanism, purview) if partitioned_repertoire is None: partitioned_repertoire_kwargs = partitioned_repertoire_kwargs or {} partitioned_repertoire = system.partitioned_repertoire( direction, partition, mechanism_measure=mechanism_measure, **partitioned_repertoire_kwargs, ) phi = _repertoire_distance( repertoire, partitioned_repertoire, direction=direction, repertoire_distance=repertoire_distance, **kwargs, ) return RepertoireIrreducibilityAnalysis( phi=phi, direction=direction, mechanism=mechanism, purview=purview, partition=partition, repertoire=repertoire, partitioned_repertoire=partitioned_repertoire, mechanism_state=state_of(mechanism, system.state), current_state=state_of(purview, system.state), specified_state=kwargs.get("state"), node_labels=system.node_labels, selectivity=None, )
[docs] def evaluate_system(self, system: Any, **kwargs: Any) -> Any: """Delegate to the IIT 3.0 ``sia`` in :mod:`pyphi.formalism.iit3`. IIT 3.0 has no specified-state phase, so measure kwargs are not threaded through this method. The system-level measure is read from ``config.formalism.iit.mechanism_phi_measure`` inside the underlying ``sia`` implementation; compatibility is checked against the active formalism's ``compatible_measures`` here. Callers attempting to pass ``system_measure`` / ``specification_measure`` receive a :class:`TypeError` rather than a silent no-op. """ check_measure_compatible(self, config.formalism.iit.mechanism_phi_measure) check_sia_tie_strategy_compatible(self, config.formalism.iit.sia_tie_resolution) check_mechanism_partition_scheme_compatible( self, config.formalism.iit.mechanism_partition_scheme ) # A System may pin its own background convention; check the value in # effect for this system, falling back to config. resolve_background = getattr(system, "_resolved_background_conditioning", None) check_background_conditioning_compatible( self, resolve_background() if resolve_background is not None else config.formalism.iit.background_conditioning, ) from pyphi.formalism.iit3 import sia as _sia return _sia(system, **kwargs)
[docs] def build_ces(self, system: Any, **kwargs: Any) -> Any: """IIT 3.0 CES is exactly the set of distinctions (no relations). Returns the distinctions specified by the system; this is the IIT 3.0 cause-effect structure. """ from pyphi.formalism.iit3 import ( _compute_distinctions as _ces, # pyright: ignore[reportPrivateUsage] ) return _ces(system, **kwargs)