6. Visualise#
What this block is. Two families of charts over the frames Blocks 3
and 5 produce. Benchmarks answer “which method found more of the known
hits, and how fast” — Altair, interactive HTML. Diagnostic plots show the
mechanism — what GMIC, temperature and disagreement did over the run —
matplotlib figures for a paper. Both live in TACTICS.library_analysis
and need the viz extra:
pip install "chem-tactics[viz]"
Importing TACTICS.library_analysis does not load matplotlib or Altair;
they are imported when you build a chart, and a missing extra raises an
ImportError that says so.
Core#
Compare two presets on the thrombin data against its exhaustive scores:
import polars as pl
from TACTICS.library_analysis import TS_Benchmarks
# one search() DataFrame per replicate, per method
runs = {
"TT-TS (recommended)": [run("recommended", seed) for seed in (1, 2)],
"RWS / CATS": [run("recommended_rws", seed) for seed in (1, 2)],
}
reference = (
pl.read_parquet(thrombin_scores())
.rename({"Product_Code": "Name", "Scores": "score"})
.select(["score", "Name"]) # same columns, same order, as search() output
)
bench = TS_Benchmarks(
no_of_cycles=2, # number of replicates per method (not search cycles)
methods_list=list(runs),
TS_runs_data=runs,
reference_data=reference,
top_n=100, # "hit" = one of the 100 best reference compounds
sort_type="minimize",
)
bench.plot_barplot_TS_results(save_path="recovery_bar.html", show_plot=False)
bench.plot_line_performance_with_error_bars(save_path="recovery_curve.html", show_plot=False)
TS_Benchmarks takes, per
method, a list of search() DataFrames — one per replicate — and a
reference frame of the ground truth. It counts how many of the top_n
best reference compounds each replicate found, and:
plot_barplot_TS_results— hits per replicate and per method, with the reference total as the ceiling;plot_line_performance_with_error_bars— fraction of the top-N recovered, for each N intop_ns, with ±1 sd bands;stripplot_TS_results— every scored product’s score, jittered by replicate and coloured by method.
Each returns an Altair chart (display it in a notebook) and writes HTML
when save_path is given.
What it produces: interactive HTML files, or chart objects for a notebook.
Build on it#
Diagnostic plots#
TACTICS.library_analysis.diagnostic_plots draws the trajectories
from 5. Inspect. Every function takes Polars frames and returns a
matplotlib.figure.Figure:
import polars as pl
from TACTICS.library_analysis.diagnostic_plots import plot_ttts_diagnostic
# the combined plots average over replicates, so they expect a `replicate`
# column; a single run is replicate 0
diag = diagnostics.with_columns(pl.lit(0).alias("replicate"))
fig = plot_ttts_diagnostic(diag, title="thrombin — TT-TS")
fig.savefig("ttts_diagnostic.png", dpi=150, bbox_inches="tight")
Single-run trajectories — one get_diagnostics() frame, any strategy
that records criticality:
plot_criticality_trajectory()— criticality per component over cycles.plot_snr_trajectory()— SNR with the threshold line (Bayes-UCB frames).plot_temperature_decomposition()— base temperature → CATS multiplier → final temperature for one component (RWS / Bayes-UCB frames).
Replicate-averaged mechanism plots — require a replicate column:
plot_rws_diagnostic()— GMIC with 95 % bands on the left axis, CATS multiplier on the right, diversity-mode shading.plot_ttts_diagnostic()— disagreement EMA and adaptedheated_scale.
Layered panels — require the search frame too, with a phase
column ("search"), replicate, and method for the last two, plus
the reagent files so product names map back to components:
plot_reagent_usage_action_panel()— heating timeline over the fraction of each component’s reagents tried per batch, new vs revisited.plot_gmic_directed_exploration()— Layer 1: which component GMIC chose to explore, and what came of it, one row per method.plot_adaptive_intensity()— Layer 2: how each method tuned exploration within the component (cats_multiplierfor RWS,heated_scalefor TT-TS).
The column contract, since nothing adds these for you:
import polars as pl
search = results.with_columns(
pl.lit("search").alias("phase"), # warm_up() rows would be "warmup"
pl.lit(0).alias("replicate"),
pl.lit("TT-TS").alias("method"),
)
diag = diagnostics.with_columns(pl.lit(0).alias("replicate"), pl.lit("TT-TS").alias("method"))
Stack several runs with pl.concat and pass replicate=None to
average, or replicate=2 to show one.
Saving#
Altair: save_path="chart.html". matplotlib: fig.savefig("fig.png",
dpi=150, bbox_inches="tight") or fig.savefig("fig.pdf") for a
manuscript.
Gotchas
TS_Benchmarks(no_of_cycles=...)is the number of replicate runs per method, not search cycles. The lists inTS_runs_datamust each have exactly that many DataFrames.The reference frame needs columns
scoreandNamein that order — the same shape assearch()output minusSMILES; a different order raises a PolarsShapeError.sort_typeonTS_Benchmarksmust match the searchmode("minimize"for docking) or the “top” reference compounds are the worst ones.The replicate-averaged and layered plots read from benchmark output with
replicate/phase/methodcolumns that a single run does not carry — add them as above.plt.show()is not called for you; in a script, save the figure.
Reference#
Visualise — TS_Benchmarks and the eight plot
functions with every argument.
Interactive: marimo edit tutorials/diagnostic_benchmark_plots.py renders
the mechanism plots over the published benchmark output;
tutorials/thompson_sampling_tutorial.py builds a TS_Benchmarks
comparison live.
Next: Extending TACTICS — write your own strategy, warmup or scorer class.