mixle.epistemic.portfolio module¶
HypothesisPortfolio – a weighted set of typed hypotheses plus an explicit open-world mass.
The program-plan’s H_t = {(h_i, w_i)} u {(_|_, w_|_)}: a sequential-Monte-Carlo particle cloud
generalized from mixle.inference.mcmc.particle_filter()’s numeric-state-only particles to
arbitrary typed hypothesis payloads, with the reserved “none of the above” mass carried as a
first-class field rather than folded into the particle list. Weights always satisfy w_open +
sum(active weights) == 1 – every mutating method returns a new portfolio with the invariant
already restored, and the constructor validates it on every construction (no silent drift).
Pruning (prune()) never deletes a hypothesis, only deactivates it – the same
never-truly-forget philosophy already used by mixle.substrate.belief’s cascading retraction –
and its freed mass folds into w_open: a pruned hypothesis was one we could no longer defend, which
is exactly what growing the “we don’t currently have an explanation” mass means (see
notes/epistemic-loop-integration-workplan.md §5 Q1). resample() delegates to the same
systematic/multinomial resampling math mixle.inference.mcmc.particle_filter() uses, applied only
to the active mass – w_open is untouched by resampling, since it isn’t a particle to resample.
- class Hypothesis(id, payload, active=True)[source]
Bases:
objectOne typed hypothesis in a portfolio.
payloadis opaque to the portfolio itself.
- class HypothesisPortfolio(hypotheses, weights, w_open=0.0)[source]
Bases:
objectA weighted, typed hypothesis set with an explicit reserved open-world mass
w_open.- Parameters:
hypotheses (Sequence[Hypothesis])
weights (np.ndarray)
w_open (float)
- reweight(observation, likelihood_fn, *, open_world_likelihood=None)[source]
Bayesian-reweight every active hypothesis by
likelihood_fn(h, observation).open_world_likelihood(observation)reweightsw_opentoo; it defaults to a flat constant baseline of1.0– an implicit “moderately plausible, independent of how badly the current hypotheses fit” prior – which is what makes the surprise mechanism work without extra wiring: when every active hypothesis’s likelihood collapses toward zero on an out-of-support observation, the (unchanged) open-world baseline dominates the renormalization andw_opengrows on its own, exactly the “the residual resists the current hypothesis schema” signal the program plan’s surprise trigger names. If every likelihood (including the open-world baseline) is zero, all mass moves tow_open– the honest “nothing, including the reserved slot, explains this” outcome, rather than raising or producing NaNs.
- resample(*, method='systematic', ess_threshold=0.5, rng=None)[source]
Resample the active particle set if effective sample size drops below
ess_threshold * n.w_openis untouched – it is a reserved mass, not a particle. Resampled duplicates of the same source hypothesis get id-suffixed copies ("h2","h2#1", …) so every hypothesis id in the returned portfolio stays unique, whichresurrect()/the journal rely on.
- prune(*, min_weight)[source]
Deactivate (never delete) active hypotheses below
min_weight; their mass folds intow_open.- Parameters:
min_weight (float)
- Return type:
HypothesisPortfolio
- resurrect(hypothesis_id, *, floor_weight=1e-3)[source]
Reactivate a deactivated hypothesis, taking its floor weight out of
w_open(mass-conserving).
- surprise_score(observation, likelihood_fn)[source]
Joint improbability of
observationunder every active hypothesis, in[0, 1).baseline / (baseline + weighted_mean_likelihood)against the same flatbaseline = 1.0reweight()uses by default – close to 0 when some active hypothesis explains the observation well, close to 1 when every active hypothesis assigns it near-zero likelihood (program plan §3.5’s “improbable under every live hypothesis” surprise condition). A heuristic scalar, not a calibrated probability – callers threshold it, this method just computes it.