mixle.task.environment module¶
Environment protocol + belief-driven interaction loop (roadmap M1) – the generic act-observe-update spine that on-the-fly simulators (M2), inversion (M3), the language<->belief bridge (M5), and environments-as-selection-pressure (L1) build on.
interact() drives ANY object satisfying the Environment protocol against a
streaming BELIEF over its latents (mixle.inference.streaming), picking actions by EIG
(reusing mixle.task.probe_policy.myopic_eig_policy() unchanged), a belief-driven greedy
heuristic, or a caller-supplied callable – then hands back a replayable
InteractionLog (mixle.task.replay).
ExplorationWorld becomes the first environment:
ExplorationEnvironment below is a THIN wrapper – it holds episode config and adapts
reset/step/action_space onto the world; ExplorationWorld itself is untouched, so
existing callers (run_episode, probe_policy) keep working exactly as before.
Belief math (see notes/designs/M1.md for the full writeup): a cell’s underlying latent
(“geology”) is fixed for the episode; each accepted survey observation is one more noisy
read of it. GaussianStreamingBelief folds those reads one at a time through
mixle.inference.streaming.StreamingEstimator with a harmonic(1.0) schedule – which
is exactly the textbook incremental-mean/-variance recursion (rho_t = 1/t), so the running
GaussianDistribution is the exact batch MLE over all reads so far, not merely an
approximation. Streaming’s own nobs bookkeeping is a decayed effective count (it does not
grow with t under a stationary rho schedule), so credible intervals track their own read
count separately for the standard-error term.
- class Environment(*args, **kwargs)[source]
Bases:
ProtocolGeneric act-observe world.
resetstarts (or restarts) an episode from a seed and returns an initial observation;action_costquotes a finite, non-negative upper bound before mutation;stepapplies one action and returns(observation, actual_cost);action_spacelists the actions currently legal to take. The actual cost must not exceed the quote. This reservation contract letsinteract()enforce one budget semantics across arbitrary environments without executing an action first and discovering afterward that it was unaffordable.
- class ExplorationEnvironment(n_cells, n_targets, budget)[source]
Bases:
objectThin
Environmentwrapper overExplorationWorld.Holds the episode config (cell/target/budget counts);
reset(seed)builds a freshExplorationWorldand keeps it asself.world(so a caller – or the"eig"policy below, which readsExplorationWorldinternals exactly the waymyopic_eig_policy()already does – can still get at the raw world).ExplorationWorld’s own public API is unmodified; this class only adapts it.
- class GaussianStreamingBelief(prior_mu=0.0, prior_sigma2=4.0, min_covar=0.05, belief_pseudo_count=0.05)[source]
Bases:
objectPer-cell streaming posterior over a scalar continuous latent (
ExplorationWorld’s per-cell “geology” value), folded in one acceptedsurveyobservation at a time viamixle.inference.streaming.StreamingEstimator– the generic online sufficient- statistic machinery M0’scondition()is built to consume once a fitted model exists. One independentGaussianDistributionper cell; an unsurveyed cell reports the shared prior.- update(obs)[source]
Fold one accepted
surveyobservation’s prospectivity read into that cell’s belief. Drill/rejected/other observations carry no continuous read and are not folded in here – a drill resolves ground truth directly, it needs no posterior (v1 scope).
- credible_interval(cell, level=0.9)[source]
A
level-credible interval for the cell’s latent: the running Gaussian’s own mean, and a standard error of the mean built from a prior/sample-variance blend (seebelief_pseudo_count) over this belief’s own read count – not the raw per-cell sample variance alone, which is degenerate (zero, beforemin_covarclamps it) at a single read and undercovers badly until several reads accumulate.levelmust be finite and in the open interval (0, 1); the returned bounds are finite and ordered by postcondition, so a caller never receives a reversed or NaN “credible” interval.
- class InteractionLog(seed, budget, policy, trace, total_cost, n_actions, n_attempts, max_steps, stop_reason)[source]
Bases:
objectOne episode’s action/observation/cost trace, replayable via
mixle.task.replay.Each recorded
"act"step bundles POLICY DECISION +env.step+ belief update as one unit (rather than recording the chosen action alone and replaying it against a bareenv.step) because a world-peeking policy like"eig"(myopic_eig_policy()readsExplorationWorld’s own RNG-backedprospectivity()while DECIDING) consumes the same environment randomness the eventual observation depends on – replaying only the action list would silently desync that RNG stream and stop reproducing bit-for-bit. Bundling the policy call into the replayed unit keeps the two draws in the same relative order both times.- Parameters:
- is_deterministic(env, belief_model)[source]
Replay this log against a fresh
env/belief_modelpair (same policy name, same seed) and confirm every recorded step reproduces exactly – the M1 replay receipt. Only named policies ("eig","greedy") can be reconstructed for replay; a log built from an arbitrary callable policy cannot (the callable itself is not serialized).
- interact(env, belief_model, *, policy='eig', budget, seed=None, max_steps=1_000)[source]
Drive the act-observe-update loop.
Resets
env, then repeatedly: pick an action overenv.action_space()(EIG / belief- greedy / a caller callable), execute it viaenv.step, fold the observation intobelief_model.update(obs), until the summed action cost would exceedbudgetor the policy/environment stops (action_space()empty, policy returnsNone, or the environment refuses the action). Every reset/act is recorded as aTraceStep(seeInteractionLogfor why policy decision + step are bundled into one"act"unit) so the returnedInteractionLogreplays deterministically viamixle.task.replay.