Source code for pyphi.substrate_generator.mechanism_combinations

# substrate_generator/mechanism_combinations.py
"""Combination strategies for composite units.

A composite unit holds several sub-mechanisms and combines their activation
probabilities into one. In the present=past substrate TPM every sub-mechanism is
evaluated at the same from-state, so a combination is simply a function of the
list of sub-mechanism probabilities.

:func:`composite` builds a unit function from a list of sub-mechanism specs and a
combination strategy.
"""

import numpy as np

from .mechanisms import MECHANISMS


[docs] def selective(probs, **kwargs): """The sub-mechanism probability farthest from 0.5 (the most decisive).""" probs = np.asarray(probs, dtype=float) return float(probs[np.argmax(np.abs(probs - 0.5))])
[docs] def average(probs, **kwargs): """The mean of the sub-mechanism probabilities.""" return float(np.mean(probs))
[docs] def maximal(probs, **kwargs): """The maximum sub-mechanism probability.""" return float(np.max(probs))
[docs] def first_necessary(probs, *, steepness=5.0, offset=0.5, **kwargs): """The first (primary) sub-mechanism, boosted toward 1 when the others are inactive — but only if the primary is already above 0.5. ``steepness`` and ``offset`` parameterize the logistic boost; they default to 5 and 0.5. """ primary = probs[0] if primary > 0.5: non_primary = np.prod([1 - p for p in probs[1:]]) max_boost = 1 - primary boost = max_boost / (1 + np.e ** (-steepness * (1 - non_primary - offset))) return float(primary + boost) return float(primary)
[docs] def integrator(probs, **kwargs): """The sum of the sub-mechanism probabilities, clamped to [0, 1].""" return float(min(1.0, max(0.0, np.sum(probs))))
[docs] def serial(probs, **kwargs): """Series combination: ``1 - prod(1 - p)`` over the sub-mechanisms.""" remainder = 1.0 for p in probs: remainder -= p * remainder return float(1 - remainder)
MECHANISM_COMBINATIONS = { "selective": selective, "average": average, "maximal": maximal, "first_necessary": first_necessary, "integrator": integrator, "serial": serial, }
[docs] def composite(sub_specs, combination="selective", **combination_kwargs): """Build a composite unit function from sub-mechanism specs. Parameters ---------- sub_specs : Iterable[Mapping] An iterable of dicts, each ``{"mechanism": name | callable, "inputs": tuple[int] | None, "params": dict}``. Each sub-mechanism is evaluated at the full from-state and reads its own ``inputs``. combination : str or Callable, optional A name in :data:`MECHANISM_COMBINATIONS` or a callable mapping a sequence of probabilities to a single probability. Defaults to ``"selective"``. Other Parameters ---------------- **combination_kwargs Passed to the combination strategy (e.g. ``steepness`` / ``offset`` for ``first_necessary``). Returns ------- Callable A unit function ``f(element, weights, state, **kwargs) -> float`` whose value is the combined probability of its sub-mechanisms. Raises ------ TypeError If a sub-spec has a key other than ``mechanism``, ``inputs`` and ``params``, or ``params`` or ``combination_kwargs`` names an option the mechanism or combination does not take. A callable that accepts any keyword is not checked. """ # Imported here: the package imports this module. from . import _check_spec from . import _named_options from . import _reject_unknown if isinstance(combination, str): combine = MECHANISM_COMBINATIONS[combination] else: combine = combination options = _named_options(combine, 1) if options is not None: _reject_unknown(combination_kwargs, options, "the combination's options") sub_specs = list(sub_specs) for k, spec in enumerate(sub_specs): _check_spec(spec, f"sub-spec {k}") resolved = [] for spec in sub_specs: mech = spec["mechanism"] func = MECHANISMS[mech] if isinstance(mech, str) else mech inputs = spec.get("inputs") inputs = tuple(inputs) if inputs is not None else None resolved.append((func, inputs, dict(spec.get("params", {})))) def composite_func(element, weights, state, **kwargs): probs = [ func(element, weights, state, inputs=inputs, **params) for func, inputs, params in resolved ] return combine(probs, **combination_kwargs) return composite_func