tbnpy.diagnostics

Checks of a model’s numbers, where tbnpy.check_model() checks its protocol:

  • run_sense_check() forward-samples the prior and reports marginals, bound violations, rank correlations, and how each discrete variable moves the others, which catches unit errors, sign flips, dead variables and NaN paths;

  • check_evidence() refuses, with an explanation, conditioning that cannot work, such as an exact observation of a deterministic continuous node;

  • posterior() wraps likelihood weighting (tbnpy.inference.sample_evidence()) and returns summaries together with the effective sample size that qualifies them.

from tbnpy import diagnostics

report = diagnostics.run_sense_check(probs, varis, n_sample=20_000)
print(report.flags)

r = diagnostics.posterior(probs, ["C"], {"OC": -0.3}, n_sample=50_000, varis=varis)
print(r.summary(varis), r.ess)

API

Model diagnostics: prior sense checks, evidence validation, and weighted posterior summaries.

Everything here is plain torch/numpy/pandas and lives in core deliberately. A pip install tbnpy user gets the same checks the GUI shows, and the GUI stays a thin presentation layer over this module rather than the place where the real work happens.

Three groups of functions:

Prior sense check

run_sense_check() forward-samples the whole model and reports marginals, bound violations, rank correlations, and how each discrete variable moves the rest. Catches unit errors, sign flips, dead variables, and NaN paths.

Evidence validation

check_evidence() refuses, with an explanation, the conditioning requests that cannot work – most importantly a hard observation of a deterministic continuous node, which has probability zero and yields a NaN posterior rather than an error.

Weighted posterior

posterior() wraps tbnpy.inference.sample_evidence(), which is likelihood weighting, and returns summaries together with the effective sample size. ESS is not decoration: at 20k particles a tail observation can leave a few hundred real ones behind a perfectly smooth-looking histogram, and nothing else in the output reveals it.

class tbnpy.diagnostics.SenseCheckReport(samples: 'pd.DataFrame', marginals: 'pd.DataFrame', discrete_pmfs: 'dict', correlation: 'pd.DataFrame', conditionals: 'dict', flags: 'pd.DataFrame', n_sample: 'int')[source]
tbnpy.diagnostics.run_sense_check(probs, varis, n_sample=20000, bounds=None, seed=0, batch_size=50000, method='spearman')[source]

Forward-sample the prior and assemble the full diagnostic report.

tbnpy.diagnostics.forward_sample_all(probs, n_sample=20000, batch_size=50000, seed=0)[source]

Prior-sample every node into a tidy (n_sample, n_vars) DataFrame.

inference.sample returns each node’s child samples only, shaped (N,) for a generated or custom single-child node and (N, n_childs) for a tabular Cpt, so both layouts are unpacked here.

tbnpy.diagnostics.plot_report(report, varis, stem, max_vars=30)[source]

Correlation heatmap plus a grid of marginal histograms.

matplotlib is imported here rather than at module scope: it is not a dependency of tbnpy, so the rest of this module works without it.

class tbnpy.diagnostics.EvidenceIssue(node: 'str', severity: 'str', issue: 'str', detail: 'str', suggestions: 'list' = <factory>)[source]
tbnpy.diagnostics.check_evidence(probs, varis, evidence) → list[source]

Check a conditioning request before spending any sampling on it.

Parameters:

evidence – A DataFrame of evidence rows, or {node: value}/{node: [values]}.

Returns:

Empty when the request is sound. Errors mean the posterior would be undefined, not merely poor.

Return type:

list[EvidenceIssue]

class tbnpy.diagnostics.PosteriorResult(samples: dict, log_weights: torch.Tensor, weights: numpy.ndarray, ess: torch.Tensor, n_sample: int, evidence: pandas.DataFrame, issues: list = <factory>)[source]

Weighted particles, their summaries, and the ESS that qualifies them.

is_degenerate(threshold=0.01)[source]

Rows whose ESS has collapsed below threshold of the particles.

summary(varis=None, row=0) → pandas.DataFrame[source]

Weighted marginal summary for one evidence row.

pmf(name, var, row=0) → pandas.DataFrame[source]

Weighted posterior pmf of a discrete variable.

tbnpy.diagnostics.posterior(probs, query_nodes, evidence, n_sample=20000, varis=None, seed=0, batch_size=50000, validate=True) → PosteriorResult[source]

Posterior particles for query_nodes given evidence.

inference.sample_evidence is likelihood weighting: it clamps the observed nodes, resamples everything else, and returns each node’s log-density. The importance weight of a particle is the sum of those log-densities over the evidence nodes only – the sampled nodes’ densities are the proposal and cancel.

Raises:

ValueError – If validate and the evidence cannot be conditioned on at all. Pass validate=False to get the degenerate result anyway.

tbnpy.diagnostics.effective_sample_size(log_weights) → torch.Tensor[source]

Kish effective sample size from unnormalised log weights.

log_weights is (n_sample,) or (n_evi, n_sample). Rows whose weights are all -inf – an impossible observation – give 0.0 rather than NaN, so the caller can report the cause instead of a silent NaN.

tbnpy.diagnostics.weighted_quantile(values, weights, quantiles)[source]

Quantiles of a weighted particle set.