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: object

One typed hypothesis in a portfolio. payload is opaque to the portfolio itself.

Parameters:
class HypothesisPortfolio(hypotheses, weights, w_open=0.0)[source]

Bases: object

A 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) reweights w_open too; it defaults to a flat constant baseline of 1.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 and w_open grows 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 to w_open – the honest “nothing, including the reserved slot, explains this” outcome, rather than raising or producing NaNs.

Parameters:
Return type:

HypothesisPortfolio

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_open is 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, which resurrect()/the journal rely on.

Parameters:
Return type:

HypothesisPortfolio

prune(*, min_weight)[source]

Deactivate (never delete) active hypotheses below min_weight; their mass folds into w_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).

Parameters:
  • hypothesis_id (str)

  • floor_weight (float)

Return type:

HypothesisPortfolio

surprise_score(observation, likelihood_fn)[source]

Joint improbability of observation under every active hypothesis, in [0, 1).

baseline / (baseline + weighted_mean_likelihood) against the same flat baseline = 1.0 reweight() 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.

Parameters:
Return type:

float