# pyright: strict
"""Protocols and registry for phi formalisms.
A *formalism* is a strategy for computing integrated information. Each
formalism bundles a partition scheme, the distance measures it accepts, and
algorithms that combine them into mechanism-level RIAs, system-level SIAs,
and Φ-structures.
This module declares the abstract Protocol shape and the global registry.
Concrete implementations live in ``pyphi.formalism.iit3`` and
``pyphi.formalism.iit4``.
"""
from __future__ import annotations
from typing import TYPE_CHECKING
from typing import Any
from typing import ClassVar
from typing import Literal
from typing import Protocol
from typing import runtime_checkable
from pyphi.registry import InstanceRegistry
if TYPE_CHECKING:
pass
__all__ = [
"ACTUAL_CAUSATION_FORMALISM_REGISTRY",
"FORMALISM_REGISTRY",
"ActualCausationFormalism",
"ActualCausationFormalismRegistry",
"ApproximateFormalism",
"ErrorInfo",
"ExactFormalism",
"FormalismRegistry",
"MeasureNotCompatibleError",
"PhiFormalism",
"check_background_conditioning_compatible",
"check_measure_compatible",
"check_mechanism_partition_scheme_compatible",
"check_sia_tie_strategy_compatible",
"check_specification_measure_compatible",
]
[docs]
class MeasureNotCompatibleError(Exception):
"""Raised when a configured measure isn't compatible with the active formalism.
Each :class:`PhiFormalism` declares ``compatible_measures`` as a frozenset
of measure names it accepts. Combinations outside that set (e.g., the IIT
4.0 formalism with the EMD distribution measure) compute a different
mathematical object than the formalism's ``φ`` definition; rejecting them
early prevents silently misleading results.
"""
[docs]
def check_measure_compatible(formalism: PhiFormalism, measure: str) -> None:
"""Raise :class:`MeasureNotCompatibleError` if ``measure`` isn't accepted
by ``formalism``.
Called from each formalism's ``evaluate_*`` methods so the failure
surface is at the dispatch boundary, not deep inside the math.
"""
if measure not in formalism.compatible_measures:
raise MeasureNotCompatibleError(
f"Measure {measure!r} is not compatible with "
f"formalism {formalism.name!r}. Compatible measures for "
f"this formalism: {sorted(formalism.compatible_measures)}. "
"If you want a different measure, set "
"config.formalism.iit.version to one whose compatible_measures "
"set contains it."
)
[docs]
def check_sia_tie_strategy_compatible(formalism: PhiFormalism, strategy: Any) -> None:
"""Raise ``ConfigurationError`` if a SIA tie-resolution strategy uses a
component the formalism's SIA result type does not support.
Called from a formalism's ``evaluate_system`` so a config assembled by
per-field assignment (which skips cross-field validation) still fails at
the dispatch boundary with a clear diagnostic, not with an
``AttributeError`` deep inside tie resolution. Formalisms whose SIA type
supports every registered strategy declare
``compatible_sia_tie_strategies = None`` and are not checked.
"""
compatible = getattr(formalism, "compatible_sia_tie_strategies", None)
if compatible is None:
return
components = (strategy,) if isinstance(strategy, str) else tuple(strategy)
for component in components:
if component not in compatible:
from pyphi.conf import ConfigurationError
raise ConfigurationError(
f"formalism.iit.sia_tie_resolution component {component!r} is "
f"not compatible with formalism {formalism.name!r}. Compatible "
f"SIA tie strategies for this formalism: {sorted(compatible)}. "
f"Fix: set formalism.iit.sia_tie_resolution to use only those "
f"(the shipped preset uses ['PHI', 'PARTITION_LEX']), or "
f"change formalism.iit.version."
)
def _validate_config_enabled() -> bool:
"""Whether reactive dispatch-boundary config checks are enabled.
Mirrors the eager constraint gate (``infrastructure.validate_config``) so
the two validation surfaces share one opt-out.
"""
from pyphi.conf import config
return bool(config.infrastructure.validate_config)
[docs]
def check_specification_measure_compatible(
formalism: PhiFormalism, measure: str
) -> None:
"""Raise :class:`MeasureNotCompatibleError` if ``measure`` isn't accepted
by ``formalism`` as a specification measure.
Called from the formalism's measure-resolution sites so a config
assembled by per-field assignment (which skips cross-field validation)
still fails at the dispatch boundary rather than silently computing a
different Φ. Formalisms without a specified-state phase declare
``compatible_specification_measures = None`` and are not checked.
Respects the ``validate_config=False`` opt-out, so a deliberately
unsupported combination can still be studied.
"""
compatible = getattr(formalism, "compatible_specification_measures", None)
if compatible is None:
return
if not _validate_config_enabled():
return
if measure not in compatible:
raise MeasureNotCompatibleError(
f"Specification measure {measure!r} is not compatible with "
f"formalism {formalism.name!r}. Compatible specification "
f"measures for this formalism: {sorted(compatible)}. Fix: set "
f"config.formalism.iit.specification_measure to one of those, "
f"or change config.formalism.iit.version."
)
[docs]
def check_background_conditioning_compatible(
formalism: PhiFormalism, value: str
) -> None:
"""Raise ``ConfigurationError`` if the cause-side background-conditioning
convention is one ``formalism`` does not define.
Called from a formalism's ``evaluate_system`` so a config assembled by
per-field assignment (which skips cross-field validation) still fails at
the dispatch boundary rather than silently computing a different phi on
proper-subset systems. Formalisms that accept any registered convention
declare ``compatible_background_conditioning = None`` and are not
checked. Respects the ``validate_config=False`` opt-out, so a
deliberately unsupported combination can still be studied.
"""
compatible = getattr(formalism, "compatible_background_conditioning", None)
if compatible is None:
return
if not _validate_config_enabled():
return
if value not in compatible:
from pyphi.conf import ConfigurationError
raise ConfigurationError(
f"background_conditioning {value!r} is not compatible with "
f"formalism {formalism.name!r}. Compatible conventions for this "
f"formalism: {sorted(compatible)}. Fix: set "
f"formalism.iit.background_conditioning to one of those (the "
f"shipped IIT 3.0 preset uses 'CONDITION_CURRENT_STATE'), or "
f"change formalism.iit.version."
)
[docs]
def check_mechanism_partition_scheme_compatible(
formalism: PhiFormalism, scheme: str
) -> None:
"""Raise ``ConfigurationError`` if a mechanism partition scheme is one
``formalism`` does not accept.
Called from a formalism's evaluation methods so a config assembled by
per-field assignment (which skips cross-field validation) still fails at
the dispatch boundary rather than silently computing phi over a different
partition family. Formalisms that accept any registered scheme declare
``compatible_mechanism_partition_schemes = None`` and are not checked.
Respects the ``validate_config=False`` opt-out, so a deliberately
unsupported combination can still be studied.
"""
compatible = getattr(formalism, "compatible_mechanism_partition_schemes", None)
if compatible is None:
return
if not _validate_config_enabled():
return
if scheme not in compatible:
from pyphi.conf import ConfigurationError
raise ConfigurationError(
f"formalism.iit.mechanism_partition_scheme={scheme!r} is not "
f"compatible with formalism {formalism.name!r}. Compatible "
f"mechanism partition schemes for this formalism: "
f"{sorted(compatible)}. Fix: set "
f"formalism.iit.mechanism_partition_scheme to one of those, or "
f"change formalism.iit.version."
)
[docs]
class ErrorInfo(Protocol):
"""Error characterization for an approximate formalism's output.
Discriminates the three flavors of approximation:
- ``upper_bound``: result is a guaranteed upper bound on the true value
(e.g., Zaeemzadeh-style certified pruning).
- ``approximation_error``: result approximates the true value with a
bounded error.
- ``different_quantity``: result computes a related but distinct
quantity (e.g., φ* vs Φ).
"""
kind: Literal["upper_bound", "approximation_error", "different_quantity"]
bound: float | None
notes: str
FORMALISM_REGISTRY: FormalismRegistry = FormalismRegistry()
"""Global registry of phi formalisms. Looked up by string name (the value
held in ``config.formalism.iit.version``)."""
ACTUAL_CAUSATION_FORMALISM_REGISTRY: ActualCausationFormalismRegistry = (
ActualCausationFormalismRegistry()
)
"""Global registry of actual-causation formalisms. Looked up by the string
held in ``config.formalism.actual_causation.version``."""