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 in top_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:

Replicate-averaged mechanism plots — require a replicate column:

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:

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 in TS_runs_data must each have exactly that many DataFrames.

  • The reference frame needs columns score and Name in that order — the same shape as search() output minus SMILES; a different order raises a Polars ShapeError.

  • sort_type on TS_Benchmarks must match the search mode ("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 / method columns 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#

VisualiseTS_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.