Observation models¶
How a hidden state produces a sensor reading. FixedSensor is a constant linear sensor; CallableSensor lets the observation noise R(x) vary with the state — the reason an agent has anything to gain from seeking information. Both satisfy the ObservationModel protocol.
ObservationModel ¶
Bases: Protocol
How a hidden state produces an observation, as a local linear-Gaussian map.
The EFE core never assumes a fixed sensor matrix; it asks the observation
model to linearize itself about a state x, getting back the local
(C, R) (the observation Jacobian and the noise covariance there). For a
fixed sensor these are constant; for a state-dependent sensor they vary.
Attributes:
| Name | Type | Description |
|---|---|---|
is_fixed |
bool
|
|
linearize ¶
gaussianize ¶
gaussianize(
x: ArrayLike, sigma: Float64[Array, "n n"]
) -> tuple[
Float64[Array, m],
Float64[Array, "m m"],
Float64[Array, "m m"],
]
Sensor's EFE ingredients (o⁺, S, R) about belief (x, sigma).
The EFE kernel calls this, not linearize: each sensor owns its own
moment-matching, so the fixed/linear path stays a bare matvec and a
nonlinear sensor (Phase 2.5) can do 2nd-order without reopening the kernel.
Returns the predicted-observation mean o⁺, its covariance S (feeds
the pragmatic term), and the conditional observation noise R at x
(feeds the epistemic ½(ln det S − ln det R)) — all computed in one pass.
Source code in src/cpomdp/observation.py
FixedSensor
dataclass
¶
A sensor whose (C, R) never change with state — the v0.2 default.
linearize returns the same stored matrices for every x: a fixed
linear sensor is its own linear approximation everywhere. This is the
regime where EFE's epistemic term is constant and collapses to LQR
(DECISIONS.md ADR-003).
Attributes:
| Name | Type | Description |
|---|---|---|
sensor_model |
Float64[Array, 'm n']
|
the observation matrix |
sensor_noise |
Float64[Array, 'm m']
|
the observation-noise covariance |
Source code in src/cpomdp/observation.py
linearize ¶
Return the stored (C, R) unchanged — the same for every x.
gaussianize ¶
gaussianize(
x: ArrayLike, sigma: Float64[Array, "n n"]
) -> tuple[
Float64[Array, m],
Float64[Array, "m m"],
Float64[Array, "m m"],
]
Exact linear ingredients (C·x, C·Σ·Cᵀ + R, R).
Source code in src/cpomdp/observation.py
tree_flatten ¶
tree_unflatten
classmethod
¶
Rebuild without re-validating — leaves may be tracers.
Source code in src/cpomdp/observation.py
CallableSensor
dataclass
¶
CallableSensor(
sensor_model: ArrayLike,
noise_fn: Callable[
[Float64[Array, n], PyTree], Float64[Array, "m m"]
],
noise_params: PyTree,
)
A sensor with state-dependent observation noise R(x) and constant C.
The observation map stays linear (constant C); the noise covariance varies
with the state via noise_fn(x, params) -> R(x). This breaks the ADR-003
fixed-sensor collapse: with R depending on the predicted state μ⁺ (and
so on the action), the epistemic term is no longer action-invariant — the agent
can act to reach states where the sensor is sharper. Mean-exact,
covariance-plug-in: o⁺ = C·μ⁺ is exact, while R(μ⁺) is a plug-in that
drops the ½tr(H_R Σ⁺) Jensen term (a deliberate first-order choice; the
nonlinear-mean 2nd-order case is NonlinearSensor, Phase 2.5).
noise_fn must return a positive-definite R(x) at every reachable state
— it is a covariance the epistemic term inverts. A non-PD R(x) has no real
½ln det, so the EFE epistemic term becomes NaN there (surfaced at action
selection, not silently wrong); this is the runtime analogue of the
construction-time positive-definite check on a fixed sensor_noise.
params is a pytree leaf (so EFE is grad-able w.r.t. it — sensor
learning); noise_fn is static aux (a callable cannot be a traced leaf).
Pass a module-level noise_fn and keep all tunables in params: a
closure/lambda is hashable only by identity and would defeat jit caching.
Source code in src/cpomdp/observation.py
linearize ¶
Local (C, R(x)) — constant C, state-dependent noise.
Source code in src/cpomdp/observation.py
gaussianize ¶
gaussianize(
x: ArrayLike, sigma: Float64[Array, "n n"]
) -> tuple[
Float64[Array, m],
Float64[Array, "m m"],
Float64[Array, "m m"],
]
Linear ingredients (C·x, C·Σ·Cᵀ + R(x), R(x)) (mean-exact, R plug-in).
Source code in src/cpomdp/observation.py
tree_flatten ¶
Children (traced): (sensor_model, noise_params); aux: noise_fn.
tree_unflatten
classmethod
¶
Rebuild without re-validating — leaves may be tracers.