Source code for pyphi.matching.perception
"""Perception: the portion of a cause-effect structure triggered by a stimulus."""
from __future__ import annotations
from dataclasses import dataclass
from functools import cached_property
from typing import TYPE_CHECKING
import numpy as np
from .triggering import triggering_coefficient
if TYPE_CHECKING:
from pyphi.models.ces import CauseEffectStructure
from pyphi.models.ces import PhiFold
from pyphi.relations import ConcreteRelations
from .triggered_tpm import TriggeredTPM
[docs]
@dataclass(frozen=True)
class Perception:
"""The triggering coefficients and perception values for one stimulus.
A view over a cause-effect structure that computes how much of the
structure's cause-effect power was triggered by ``stimulus``, without
modifying the structure. ``ces`` must be the structure triggered by
``stimulus``: its system state must equal the state the stimulus triggers,
which ``__post_init__`` checks against ``triggered_tpm``.
Attributes
----------
ces : CauseEffectStructure
The Φ-structure unfolded from the triggered system state. An analytical
relation summary is materialized on first use of any per-relation quantity.
triggered_tpm : TriggeredTPM
The fixed-lag response distribution supplying triggering coefficients.
stimulus : tuple of int
The sensory-interface state that triggered ``ces``.
"""
ces: CauseEffectStructure
triggered_tpm: TriggeredTPM
stimulus: tuple[int, ...]
def __post_init__(self):
sia = self.ces.sia
if tuple(sia.node_indices) != tuple(self.triggered_tpm.system_indices):
raise ValueError(
"ces system nodes do not match the triggered TPM system units"
)
triggered = self.triggered_tpm.argmax_state(self.stimulus)
if tuple(sia.current_state) != tuple(triggered):
raise ValueError(
f"ces system state {tuple(sia.current_state)} is not the state "
f"triggered by stimulus {self.stimulus} ({tuple(triggered)})"
)
[docs]
@cached_property
def triggering_coefficients(self) -> dict:
"""Mapping ``{mechanism: TriggeringCoefficient}``, one per distinction.
Keyed by each distinction's mechanism, evaluated at that distinction's
mechanism state for this stimulus.
"""
return {
d.mechanism: triggering_coefficient(
self.triggered_tpm, d.mechanism, d.mechanism_state, self.stimulus
)
for d in self.ces.distinctions
}
@cached_property
def _relations(self) -> ConcreteRelations:
"""The enumerable relation set of ``ces``.
Per-relation perception values (Eqs. 9-13, 19) require individual
relations; an analytical relation summary is materialized here on
first use, at the same cost as computing the structure with the
concrete backend.
"""
from pyphi.relations import AnalyticalRelations
relations = self.ces.relations
if isinstance(relations, AnalyticalRelations):
return relations.materialize()
return relations # type: ignore[return-value] # Base Relations may be AnalyticalRelations, materialized above
[docs]
def distinction_perception(self, distinction) -> float:
"""Perception value of a distinction, t(x, m) · φ_d (Eq. 8)."""
t = self.triggering_coefficients[distinction.mechanism].value
return t * float(distinction.phi)
[docs]
def relation_perception(self, relation) -> float:
"""Perception value of a relation, t(x, r(d)) · φ_r (Eqs. 9-10).
The relation's triggering coefficient t(x, r(d)) is the unweighted mean
of the triggering coefficients of the distinctions it binds (Eq. 9), so
the perception value is the full relation φ_r times that mean (Eq. 10).
"""
mean_t = float(
np.mean(
[self.triggering_coefficients[rel.mechanism].value for rel in relation]
)
)
return float(relation.phi) * mean_t
[docs]
def fold_perception(self, fold: PhiFold) -> float:
"""Perception value of a single distinction's Φ-fold (Eq. 11).
For the Φ-fold of a mechanism m — the distinction and every relation
involving it — returns the seed's triggering coefficient t(x, m)
times the fold's contribution to Φ (Eq. 3): the distinction's φ plus
each incident relation's φ divided by its degree. ``fold`` must
contain exactly one distinction (its seed).
"""
(seed,) = fold.distinctions
t = self.triggering_coefficients[seed.mechanism].value
return t * fold.big_phi_contribution
[docs]
@cached_property
def richness(self) -> float:
"""Total perceptual richness P(x, y), summed over the structure (Eq. 13).
The sum of the perception values of every distinction and relation in
``ces``: the quantity of intrinsic meaning the stimulus triggered.
"""
distinctions = sum(self.distinction_perception(d) for d in self.ces.distinctions)
relations = sum(self.relation_perception(r) for r in self._relations)
return distinctions + relations