Skip to content

Expected free energy

The scalar each candidate action is scored by — G = pragmatic − epistemic. The pragmatic term pulls toward preferred outcomes; the epistemic term rewards actions that sharpen the belief. Lower G is better.

expected_free_energy

expected_free_energy(
    model: LinearGaussianModel,
    belief: Belief,
    action: Float64[Array, p],
    preference: Preference,
) -> tuple[
    Float64[Array, ""], dict[str, Float64[Array, ""]]
]

Expected Free Energy of taking action from belief, and its split.

Computes G = pragmatic − epistemic for the locked linear-Gaussian definition documented at the top of this module. Pure jnp, so it composes under jit/vmap/grad — in particular vmap/grad over a batch of candidate action vectors (with model/belief/preference held fixed), which is how EFESelector will search.

Parameters:

Name Type Description Default
model LinearGaussianModel

The generative model. Must have a control matrix (an action has no meaning without one). Its observation supplies the local (C, R); None means the fixed sensor (sensor_model, sensor_noise).

required
belief Belief

The current belief (μ, Σ).

required
action Float64[Array, p]

The candidate action a, shape (p,).

required
preference Preference

The goal as an OBSERVATION-space Preferencegoal is a preferred observation g (shape (m,)) and precision is Λ (shape (m, m)). See FRAGILE(lit) #1 in the module docstring.

required

Returns:

Type Description
Float64[Array, '']

(G, {"pragmatic": ..., "epistemic": ...}) — the scalar EFE and its two

dict[str, Float64[Array, '']]

non-negative components. Lower G is preferred.

Raises:

Type Description
ValueError

If the model has no control matrix.

Source code in src/cpomdp/efe.py
def expected_free_energy(
    model: LinearGaussianModel,
    belief: Belief,
    action: Float64[Array, "p"],
    preference: "Preference",
) -> tuple[Float64[Array, ""], dict[str, Float64[Array, ""]]]:
    """Expected Free Energy of taking ``action`` from ``belief``, and its split.

    Computes ``G = pragmatic − epistemic`` for the locked linear-Gaussian
    definition documented at the top of this module. Pure ``jnp``, so it composes
    under ``jit``/``vmap``/``grad`` — in particular ``vmap``/``grad`` over a batch
    of candidate ``action`` vectors (with ``model``/``belief``/``preference`` held
    fixed), which is how ``EFESelector`` will search.

    Args:
        model: The generative model. Must have a control matrix (an action has no
            meaning without one). Its ``observation`` supplies the local ``(C, R)``;
            ``None`` means the fixed sensor ``(sensor_model, sensor_noise)``.
        belief: The current belief ``(μ, Σ)``.
        action: The candidate action ``a``, shape ``(p,)``.
        preference: The goal as an OBSERVATION-space ``Preference`` — ``goal`` is a
            preferred observation ``g`` (shape ``(m,)``) and ``precision`` is ``Λ``
            (shape ``(m, m)``). See FRAGILE(lit) #1 in the module docstring.

    Returns:
        ``(G, {"pragmatic": ..., "epistemic": ...})`` — the scalar EFE and its two
        non-negative components. Lower ``G`` is preferred.

    Raises:
        ValueError: If the model has no control matrix.
    """
    if model.control is None:
        raise ValueError(
            "expected_free_energy needs a model with a control matrix; an action "
            "has no effect on a control-free (pure-tracking) model."
        )
    control = model.control  # narrowed to Array by the guard above
    action = jnp.asarray(action, dtype=float)
    step = _efe_step(
        model,
        belief.mean,
        belief.cov,
        control,
        action,
        preference.goal,
        preference.precision,
    )
    return step.g, {"pragmatic": step.pragmatic, "epistemic": step.epistemic}