5. Inspect#

What this block is. The results DataFrame tells you which products scored well. The posteriors and the per-cycle trajectory tell you why — which component the search decided was solved, when, and which reagents carry the signal. That is the SAR the search has learned, and it is available from the same sampler object once the search ends.

Core#

Turn on track_diagnostics before building the sampler, then ask for three things after search():

config.track_diagnostics = True          # record per-cycle component state
config.seed = 1

sampler = ThompsonSampler.from_config(config)
sampler.warm_up(num_warmup_trials=config.num_warmup_trials)
results = sampler.search(num_cycles=config.num_ts_iterations)

diagnostics = sampler.get_diagnostics()          # one row per (cycle, component)
landscape = sampler.get_posterior_landscape()    # one row per reagent: mean, std, n_samples
summary = sampler.get_sar_summary()              # dict: per-component structure + prose
sampler.close()

print(diagnostics.columns)
print(summary["landscape_type"])

get_diagnostics()

One row per (cycle, component): what the strategy knew and did at that moment. Columns depend on the strategy (below); every schema shares current_cycle, component_idx and criticality. Empty for Greedy/UCB/ε-greedy, which keep no component state.

get_posterior_landscape()

One row per reagent — component_idx, reagent_name, mean, std, n_samples. The learned SAR, as a table. Works without track_diagnostics.

get_sar_summary()

A dict: per-component entropy, concentration, Gini, top-1/top-5 share, SNR, the dominant reagent and a convergence verdict; landscape_type (structured_SAR / diffuse_SAR / mixed / insufficient_data); summary_text in prose; and, when diagnostics were tracked, convergence_dynamics per component.

What it produces: two Polars DataFrames and a dict. Write the frames to Parquet — everything in Build on it and in 6. Visualise runs on the saved files without a sampler.

Build on it#

Reading the trajectory#

Each recommended strategy records what drives its decisions. The columns worth watching:

Top-Two TS (12 columns)

gmic and criticality (the component’s GMIC), is_heated, heated_scale / cooled_scale / effective_scale (what the posterior std was multiplied by), disagreement_ema (how often the two draws disagreed — the signal the adaptive scale follows), n_active_reagents.

Roulette wheel / CATS (18 columns)

gmic with its parts signal_var / mean_noise_var; divergence against divergence_threshold and the resulting is_stable / cats_mode ("diversity" until posteriors settle, then GMIC-driven); base_tempcats_multiplierfinal_temperature; ema_relative_gmic.

Bayes-UCB (18 columns)

criticality from the z-score softmax with snr, participation_ratio / effective_n, sharpening_factor, the observation-gated criticality_weight and its decay, and the percentile pipeline base_tempcats_multiplierfinal_temperature.

A component whose gmic climbs and stays high early is one the search considered solved; one that stays flat is where the budget went.

Analysis functions#

TACTICS.thompson_sampling.diagnostics is Polars in, Polars out, no sampler needed:

from TACTICS.thompson_sampling.diagnostics import (
    compute_posterior_entropy, compute_convergence_point, format_sar_report,
)

print(format_sar_report(summary))
entropy = compute_posterior_entropy(landscape, mode="minimize")
converged = compute_convergence_point(diagnostics, threshold=0.3)
print(entropy.join(converged, on="component_idx"))

diagnostics.write_parquet("diagnostics.parquet")   # for post-hoc analysis and plots
landscape.write_parquet("landscape.parquet")

Many runs#

To compare methods or replicates, save each run’s diagnostics with a replicate (and method) column added, concatenate, and hand the result to the plots in 6. Visualise — they average across replicates and draw confidence bands.

Gotchas

  • track_diagnostics must be set before from_config / construction. Setting it afterwards records nothing.

  • The diagnostics schema is strategy-specific; code that reads disagreement_ema will fail on an RWS run. Branch on "disagreement_ema" in df.columns or on the strategy you ran.

  • get_sar_summary() needs observations: with very few cycles the verdict is insufficient_data and the per-component numbers are NaN.

  • compute_posterior_entropy is direction-aware — pass mode or a minimize run reads as inverted.

Reference#

Inspect — the three accessors, the six analysis functions, and the full column lists.

Search Performance Metrics for TACTICS — the metrics behind these numbers.

Next: 6. Visualise — pictures of all of this.