Skill Tracer
Skills › Scientific Agent Skills › pytdc › utilities.md

utilities.md

397 lines · skills/pytdc/references/utilities.md at 5f2bc03

Open on GitHub

Most marks come from matching words in the file, so treat them as pointers.

1# Splits, evaluators, and benchmark groups
2
3This reference describes behavior verified in the **PyTDC 1.1.15** source
4distribution on 2026-10-01. The official website documents user-facing intent;
5source inspection resolves exact method spellings and edge cases.
6

Splits, evaluators, and benchmark groups

This reference describes behavior verified in the PyTDC 1.1.15 source distribution on 2026-10-01. The official website documents user-facing intent; source inspection resolves exact method spellings and edge cases.

Raw lines 1 to 6
7## Split API overview
8
9Ordinary loaders return:
11```python
13 "train": train_frame,
14 "valid": validation_frame,
15 "test": test_frame,
17```
19The key is `valid`, not `val`. Generic defaults are:
21```python
22split = data.get_split(
23 method="random",
24 seed=42,
25 frac=[0.7, 0.1, 0.2],
27```
29Fractions are train/validation/test and should be finite, non-negative, and sum to
30one. Upstream does not consistently validate this before arithmetic; the bundled
31CLI does.

Split API overview

Ordinary loaders return:

{
    "train": train_frame,
    "valid": validation_frame,
    "test": test_frame,
}

The key is valid, not val. Generic defaults are:

split = data.get_split(
    method="random",
    seed=42,
    frac=[0.7, 0.1, 0.2],
)

Fractions are train/validation/test and should be finite, non-negative, and sum to one. Upstream does not consistently validate this before arithmetic; the bundled CLI does.

Raw lines 7 to 32
33### Loader-specific methods
35| Loader family | Verified methods | Additional arguments |
36|---|---|---|
37| Single prediction | `random`, `scaffold`, internal `cold_<entity>` | none |
38| Pair prediction (`DTI`, `DDI`, etc.) | `random`, `cold_split`, entity aliases such as `cold_drug`, `combination`, `time` | `column_name`, `time_column` |
39| General multi-prediction frame | `random`, `cold_split`, `combination` | `column_name` |
40| `MolGen`, `Reaction`, `RetroSyn` | `random` | none |
41| Benchmark train/valid | group metadata chooses `scaffold`, `random`, `combination`, or `group` | `benchmark`, `split_type`, `seed` |
43The generic official cold-start spelling is:
45```python
46split = data.get_split(
47 method="cold_split",
48 column_name=["Drug", "Target"],
49 seed=42,
50 frac=[0.7, 0.1, 0.2],
52```
54Inspect `data.get_data().columns` first. A DTI frame commonly uses `Drug` and
55`Target`, but other tasks have different entity names. Prefer `cold_split` with
56explicit columns over inferred aliases.

Loader-specific methods

Loader familyVerified methodsAdditional arguments
Single predictionrandom, scaffold, internal cold_<entity>none
Pair prediction (DTI, DDI, etc.)random, cold_split, entity aliases such as cold_drug, combination, timecolumn_name, time_column
General multi-prediction framerandom, cold_split, combinationcolumn_name
MolGen, Reaction, RetroSynrandomnone
Benchmark train/validgroup metadata chooses scaffold, random, combination, or groupbenchmark, split_type, seed

The generic official cold-start spelling is:

split = data.get_split(
    method="cold_split",
    column_name=["Drug", "Target"],
    seed=42,
    frac=[0.7, 0.1, 0.2],
)

Inspect data.get_data().columns first. A DTI frame commonly uses Drug and Target, but other tasks have different entity names. Prefer cold_split with explicit columns over inferred aliases.

Raw lines 33 to 57
58## Random split details
60`create_fold`:
621. samples test rows with `random_state=seed`;
632. samples validation rows from the remainder with **fixed**
64 `random_state=1`;
653. assigns remaining rows to train;
664. resets partition indices.
68Consequences:
70- the supplied seed changes test membership;
71- it does not independently seed validation sampling;
72- integer rounding and Pandas sampling can make exact counts differ from naïve
73 multiplication;
74- a different seed is not a guarantee that every partition changes.
76Record content hashes or stable IDs when exact split reproducibility matters.

Random split details

create_fold:

  1. samples test rows with random_state=seed;
  2. samples validation rows from the remainder with fixed random_state=1;
  3. assigns remaining rows to train;
  4. resets partition indices.

Consequences:

  • the supplied seed changes test membership;
  • it does not independently seed validation sampling;
  • integer rounding and Pandas sampling can make exact counts differ from naïve multiplication;
  • a different seed is not a guarantee that every partition changes.

Record content hashes or stable IDs when exact split reproducibility matters.

Raw lines 58 to 77
78## Scaffold split details
80The official generic documentation limits scaffold split to molecule-based
81single-instance ADME, Tox, and HTS tasks. In 1.1.15 the implementation:
83- requires RDKit;
84- parses the configured molecular entity as SMILES;
85- computes Bemis–Murcko scaffold strings with `includeChirality=False`;
86- groups rows by exact scaffold string;
87- shuffles large and small scaffold groups with `seed`;
88- greedily assigns whole groups to partitions;
89- omits SMILES that raise during scaffold generation.
91The requested row fractions are targets, not guarantees, because whole scaffold
92groups are assigned together. “Scaffold split” means exact computed scaffold groups
93do not cross partitions in that implementation. It does **not** establish that:
95- close analogs or similar scaffolds cannot cross;
96- duplicates, labels, assay batches, sources, or dates are isolated;
97- stereochemistry is isolated;
98- invalid/missing structures are represented;
99- preprocessing performed before splitting did not leak information.
101Audit exact structures, scaffolds, identifiers, labels, provenance, and temporal
102fields appropriate to the scientific question. Use cautious language such as
103“partitioned by PyTDC's 1.1.15 Murcko-scaffold implementation,” not “leakage-free.”

Scaffold split details

The official generic documentation limits scaffold split to molecule-based single-instance ADME, Tox, and HTS tasks. In 1.1.15 the implementation:

  • requires RDKit;
  • parses the configured molecular entity as SMILES;
  • computes Bemis–Murcko scaffold strings with includeChirality=False;
  • groups rows by exact scaffold string;
  • shuffles large and small scaffold groups with seed;
  • greedily assigns whole groups to partitions;
  • omits SMILES that raise during scaffold generation.

The requested row fractions are targets, not guarantees, because whole scaffold groups are assigned together. “Scaffold split” means exact computed scaffold groups do not cross partitions in that implementation. It does not establish that:

  • close analogs or similar scaffolds cannot cross;
  • duplicates, labels, assay batches, sources, or dates are isolated;
  • stereochemistry is isolated;
  • invalid/missing structures are represented;
  • preprocessing performed before splitting did not leak information.

Audit exact structures, scaffolds, identifiers, labels, provenance, and temporal fields appropriate to the scientific question. Use cautious language such as “partitioned by PyTDC's 1.1.15 Murcko-scaffold implementation,” not “leakage-free.”

Raw lines 78 to 104
105## Cold split details
107`cold_split` samples unique values independently for each requested column, then:
109- keeps test rows satisfying all sampled test-entity memberships;
110- removes any row containing a test entity from the train/validation pool;
111- samples validation entity values from the remainder;
112- keeps validation rows satisfying all validation memberships;
113- removes validation entities from train.
115For multiple columns this intersection/removal process can discard many
116cross-combination rows, produce empty validation/test partitions, and yield row
117fractions far from `frac`. PyTDC raises `ValueError` when test or validation is
118empty.
120Exact values in each requested column are designed to be disjoint across returned
121partitions. That is a narrow entity-overlap property, not proof against:
123- aliases or duplicated entities with different identifiers;
124- homologous targets or structurally near-identical compounds;
125- shared higher-level groups;
126- preprocessing or label leakage.
128The bundled loader CLI reports pairwise exact-value overlap counts for requested
129columns without making a broader claim.
131`cold_drug_target` is not a 1.1.15 method. Use:
133```python
134data.get_split(
135 method="cold_split",
136 column_name=["Drug", "Target"],
137 seed=42,
139```

Cold split details

cold_split samples unique values independently for each requested column, then:

  • keeps test rows satisfying all sampled test-entity memberships;
  • removes any row containing a test entity from the train/validation pool;
  • samples validation entity values from the remainder;
  • keeps validation rows satisfying all validation memberships;
  • removes validation entities from train.

For multiple columns this intersection/removal process can discard many cross-combination rows, produce empty validation/test partitions, and yield row fractions far from frac. PyTDC raises ValueError when test or validation is empty.

Exact values in each requested column are designed to be disjoint across returned partitions. That is a narrow entity-overlap property, not proof against:

  • aliases or duplicated entities with different identifiers;
  • homologous targets or structurally near-identical compounds;
  • shared higher-level groups;
  • preprocessing or label leakage.

The bundled loader CLI reports pairwise exact-value overlap counts for requested columns without making a broader claim.

cold_drug_target is not a 1.1.15 method. Use:

data.get_split(
    method="cold_split",
    column_name=["Drug", "Target"],
    seed=42,
)
Raw lines 105 to 140
141## Combination and time splits

Combination and time splits

Raw lines 141 to 142
143### Combination
145The built-in `combination` implementation is designed for DrugSyn data with
146`Drug1_ID`, `Drug2_ID`, and `Cell_Line_ID`. It separates drug-pair combinations
147across partitions while representing cell lines.
149In 1.1.15 it adds an internal `concat` column and does not remove it consistently
150from every returned partition. Inspect schemas rather than assuming identical
151columns. Do not apply it generically to DDI or DTI.

Combination

The built-in combination implementation is designed for DrugSyn data with Drug1_ID, Drug2_ID, and Cell_Line_ID. It separates drug-pair combinations across partitions while representing cell lines.

In 1.1.15 it adds an internal concat column and does not remove it consistently from every returned partition. Inspect schemas rather than assuming identical columns. Do not apply it generically to DDI or DTI.

Raw lines 143 to 152
153### Time
155Pair loaders use:
157```python
158split = data.get_split(
159 method="time",
160 time_column="Year",
161 frac=[0.7, 0.1, 0.2],
163```
165The verified built-in dataset case is `DTI(name="BindingDB_Patent")`, whose loader
166adds `Year`. The implementation sorts by the time column and returns an additional
167`split_time` summary. It does not use `seed`.
169The spelling `temporal` is unsupported. Time boundaries can contain ties and the
170implementation uses boundary comparisons, so inspect timestamps and counts.
172`stratified=True` is not a supported `get_split` argument in these loaders.

Time

Pair loaders use:

split = data.get_split(
    method="time",
    time_column="Year",
    frac=[0.7, 0.1, 0.2],
)

The verified built-in dataset case is DTI(name="BindingDB_Patent"), whose loader adds Year. The implementation sorts by the time column and returns an additional split_time summary. It does not use seed.

The spelling temporal is unsupported. Time boundaries can contain ties and the implementation uses boundary comparisons, so inspect timestamps and counts.

stratified=True is not a supported get_split argument in these loaders.

Raw lines 153 to 173
174## Evaluator registry
176Discover exact names from the installed package:
178```bash
179python scripts/discover_metadata.py --kind evaluators --limit 100
180```
182Verified scalar registry names include:
184```text
185roc-auc, f1, pr-auc, precision, recall, accuracy,
186mse, rmse, mae, r2, pcc, spearman,
187micro-f1, macro-f1, kappa, avg-roc-auc,
188rp@k, pr@k, range_logAUC
189```
191Generation/distribution names include:
193```text
194novelty, diversity, uniqueness, validity, fcd_distance, kl_divergence
195```
197Coordinate names include `rmsd` and `kabsch_rmsd`. Metadata also lists `smina`, but
198the 1.1.15 `Evaluator.assign_evaluator` implementation does not bind an evaluator
199function for it; treat `Evaluator("smina")` as an unresolved upstream inconsistency,
200not supported usage.
202Always pass exact registry names. Fuzzy matching exists, but aliases such as
203`Pearson`, `Micro-AUPR`, and `Macro-AUPR` are not registered.

Evaluator registry

Discover exact names from the installed package:

python scripts/discover_metadata.py --kind evaluators --limit 100

Verified scalar registry names include:

roc-auc, f1, pr-auc, precision, recall, accuracy,
mse, rmse, mae, r2, pcc, spearman,
micro-f1, macro-f1, kappa, avg-roc-auc,
rp@k, pr@k, range_logAUC

Generation/distribution names include:

novelty, diversity, uniqueness, validity, fcd_distance, kl_divergence

Coordinate names include rmsd and kabsch_rmsd. Metadata also lists smina, but the 1.1.15 Evaluator.assign_evaluator implementation does not bind an evaluator function for it; treat Evaluator("smina") as an unresolved upstream inconsistency, not supported usage.

Always pass exact registry names. Fuzzy matching exists, but aliases such as Pearson, Micro-AUPR, and Macro-AUPR are not registered.

Raw lines 174 to 204
Runs code python scripts/discover_metadata.py :179
205### Inputs and direction
207| Metrics | Input | Better direction |
208|---|---|---|
209| `mse`, `rmse`, `mae` | continuous truth and predictions | lower |
210| `r2`, `pcc`, `spearman` | continuous truth and predictions | higher |
211| `roc-auc`, `pr-auc`, `range_logAUC` | binary truth and real-valued scores | higher |
212| `accuracy`, `precision`, `recall`, `f1` | binary truth and scores plus optional threshold | higher |
213| `micro-f1`, `macro-f1`, `kappa` | integer class labels | higher |
214| `avg-roc-auc` | per-instance sequences of binary truth/scores | higher |
215| `pr@k`, `rp@k` | binary truth/scores and target recall/precision | higher |
216| `validity`, `uniqueness`, `novelty`, `diversity` | SMILES collections (some also need a reference set) | higher by their documented definitions |
217| `kl_divergence` | generated and reference SMILES | higher; mean of `exp(-KL)` terms |
218| `fcd_distance` | generated and reference SMILES | backend-dependent; see below |
219| `rmsd`, `kabsch_rmsd` | paired coordinate arrays | lower |
221This table describes evaluator semantics, not every benchmark's leaderboard
222objective. Use `bm_metric_names` or the official benchmark page for the chosen
223benchmark. Never infer a dataset's metric from “classification” or “regression”
224alone.

Inputs and direction

MetricsInputBetter direction
mse, rmse, maecontinuous truth and predictionslower
r2, pcc, spearmancontinuous truth and predictionshigher
roc-auc, pr-auc, range_logAUCbinary truth and real-valued scoreshigher
accuracy, precision, recall, f1binary truth and scores plus optional thresholdhigher
micro-f1, macro-f1, kappainteger class labelshigher
avg-roc-aucper-instance sequences of binary truth/scoreshigher
pr@k, rp@kbinary truth/scores and target recall/precisionhigher
validity, uniqueness, novelty, diversitySMILES collections (some also need a reference set)higher by their documented definitions
kl_divergencegenerated and reference SMILEShigher; mean of exp(-KL) terms
fcd_distancegenerated and reference SMILESbackend-dependent; see below
rmsd, kabsch_rmsdpaired coordinate arrayslower

This table describes evaluator semantics, not every benchmark's leaderboard objective. Use bm_metric_names or the official benchmark page for the chosen benchmark. Never infer a dataset's metric from “classification” or “regression” alone.

Raw lines 205 to 225
226### Call behavior
228```python
229from tdc import Evaluator
231mae = Evaluator("MAE")(y_true, y_pred)
232auroc = Evaluator("ROC-AUC")(y_true_binary, predicted_scores)
233spearman = Evaluator("Spearman")(y_true, y_pred)
234```
236Thresholded `accuracy`, `precision`, `recall`, and `f1` default to 0.5 and convert
237scores with `score > threshold`; a score exactly equal to the threshold becomes
238class 0. `PR@K` and `RP@K` default to **0.5 through Evaluator**, overriding the
239underlying functions' 0.9 default. Pass `threshold=0.9` explicitly when desired. Spearman returns
240only the correlation coefficient from SciPy's result.
242`pr-auc` calls scikit-learn `average_precision_score`, not trapezoidal PR-curve
243integration. `avg-roc-auc` averages over axis 0 (one AUC per row/sequence), so
244transpose task matrices only when that matches the intended unit of averaging.
245`range_logAUC` defaults to FPR range `(0.001, 0.1)`; the public wrapper does not
246forward `FPR_range`. Likewise `Evaluator("kabsch_rmsd")` performs rotation without
247centroid translation and ignores a `translate` keyword; center paired coordinates
248first or call `tdc.evaluator.kabsch_rmsd(P, Q, translate=True)` directly.
250`kl_divergence` averages ten `exp(-KL)` terms over molecular descriptors and
251internal similarity. Higher means closer distributions; it is not raw KL.
252Unpatched 1.1.15 also imports removed `scipy.histogram`: KL fails on the tested
253modern SciPy stack. A test-only alias to `numpy.histogram` reproduced the intended
254transform, but is not an upstream fix or a supported unpatched workflow. Its KDE
255can fail for tiny, constant, or singular samples.
256`fcd_distance` tries TensorFlow/FCD first, returning `exp(-0.2 * raw_FCD)` (higher
257is closer), then falls back to PyTorch/fcd_torch, returning raw FCD (lower is
258closer). Record the selected backend and transformation; do not compare these
259values directly or infer direction from the registry name. Optional FCD models
260were source-verified only, not downloaded/executed in this refresh.
262Validate lengths, shapes, label encoding, missing values, score calibration, and
263class presence before calling. ROC-AUC is undefined when only one class is present.

Call behavior

from tdc import Evaluator

mae = Evaluator("MAE")(y_true, y_pred)
auroc = Evaluator("ROC-AUC")(y_true_binary, predicted_scores)
spearman = Evaluator("Spearman")(y_true, y_pred)

Thresholded accuracy, precision, recall, and f1 default to 0.5 and convert scores with score > threshold; a score exactly equal to the threshold becomes class 0. PR@K and RP@K default to 0.5 through Evaluator, overriding the underlying functions' 0.9 default. Pass threshold=0.9 explicitly when desired. Spearman returns only the correlation coefficient from SciPy's result.

pr-auc calls scikit-learn average_precision_score, not trapezoidal PR-curve integration. avg-roc-auc averages over axis 0 (one AUC per row/sequence), so transpose task matrices only when that matches the intended unit of averaging. range_logAUC defaults to FPR range (0.001, 0.1); the public wrapper does not forward FPR_range. Likewise Evaluator("kabsch_rmsd") performs rotation without centroid translation and ignores a translate keyword; center paired coordinates first or call tdc.evaluator.kabsch_rmsd(P, Q, translate=True) directly.

kl_divergence averages ten exp(-KL) terms over molecular descriptors and internal similarity. Higher means closer distributions; it is not raw KL. Unpatched 1.1.15 also imports removed scipy.histogram: KL fails on the tested modern SciPy stack. A test-only alias to numpy.histogram reproduced the intended transform, but is not an upstream fix or a supported unpatched workflow. Its KDE can fail for tiny, constant, or singular samples. fcd_distance tries TensorFlow/FCD first, returning exp(-0.2 * raw_FCD) (higher is closer), then falls back to PyTorch/fcd_torch, returning raw FCD (lower is closer). Record the selected backend and transformation; do not compare these values directly or infer direction from the registry name. Optional FCD models were source-verified only, not downloaded/executed in this refresh.

Validate lengths, shapes, label encoding, missing values, score calibration, and class presence before calling. ROC-AUC is undefined when only one class is present.

Raw lines 226 to 264
265## BenchmarkGroup API
267Public specialized imports in 1.1.15 are:
269```python
270from tdc.benchmark_group import (
271 admet_group,
272 docking_group,
273 drugcombo_group,
274 dti_dg_group,
276```
278The generic top-level import is deprecated:
280```python
281# Compatibility only; emits a deprecation message.
282from tdc import BenchmarkGroup
283```
285Use a specialized class. Construction can download and extract an entire group
286archive:
288```python
289from tdc.benchmark_group import admet_group
291group = admet_group(path=".pytdc-benchmarks")
292```
294Do this only after user approval.

BenchmarkGroup API

Public specialized imports in 1.1.15 are:

from tdc.benchmark_group import (
    admet_group,
    docking_group,
    drugcombo_group,
    dti_dg_group,
)

The generic top-level import is deprecated:

# Compatibility only; emits a deprecation message.
from tdc import BenchmarkGroup

Use a specialized class. Construction can download and extract an entire group archive:

from tdc.benchmark_group import admet_group

group = admet_group(path=".pytdc-benchmarks")

Do this only after user approval.

Raw lines 265 to 295
296### Retrieve fixed test and train/validation data
298```python
299benchmark = group.get("Caco2_Wang")
300name = benchmark["name"]
301train_val = benchmark["train_val"]
302test = benchmark["test"]
304train, valid = group.get_train_valid_split(
305 seed=1,
306 benchmark=name,
307 split_type="default",
309```
311There is no general `get_test()` method in 1.1.15. `group.get()` returns
312`train_val`, `test`, and normalized `name`. `get_train_valid_split` reads the
313downloaded train/validation file and applies group metadata. The held-out test set
314is fixed. The base splitter uses `[0.875, 0.125, 0.0]` of train_val for ordinary
315train/valid splits; the `group` path instead holds out 0.2 within `Year`. With zero
316test fraction, the random path's fixed validation seed makes its split identical
317across the requested seeds. Vary model training seeds independently.

Retrieve fixed test and train/validation data

benchmark = group.get("Caco2_Wang")
name = benchmark["name"]
train_val = benchmark["train_val"]
test = benchmark["test"]

train, valid = group.get_train_valid_split(
    seed=1,
    benchmark=name,
    split_type="default",
)

There is no general get_test() method in 1.1.15. group.get() returns train_val, test, and normalized name. get_train_valid_split reads the downloaded train/validation file and applies group metadata. The held-out test set is fixed. The base splitter uses [0.875, 0.125, 0.0] of train_val for ordinary train/valid splits; the group path instead holds out 0.2 within Year. With zero test fraction, the random path's fixed validation seed makes its split identical across the requested seeds. Vary model training seeds independently.

Raw lines 296 to 318
319### One-run evaluation
321Predictions must align exactly with the downloaded test-frame row order:
323```python
324predictions = {name: y_pred_test}
325result = group.evaluate(predictions)
326# {normalized_name: {metric_name: value}}
327```
329Do not include test labels as model features, generate predictions from test labels,
330or tune against repeated test evaluations.

One-run evaluation

Predictions must align exactly with the downloaded test-frame row order:

predictions = {name: y_pred_test}
result = group.evaluate(predictions)
# {normalized_name: {metric_name: value}}

Do not include test labels as model features, generate predictions from test labels, or tune against repeated test evaluations.

Raw lines 319 to 331
332### Multi-run aggregation
334```python
335prediction_runs = [
336 {name: y_pred_seed_1},
337 {name: y_pred_seed_2},
338 {name: y_pred_seed_3},
339 {name: y_pred_seed_4},
340 {name: y_pred_seed_5},
342summary = group.evaluate_many(prediction_runs)
343# {normalized_name: [mean, population_standard_deviation]}
344```
346Single-run scores are rounded to three decimals before `evaluate_many` computes
347population standard deviation (`ddof=0`), then rounds the aggregate to three
348decimals. Retain raw per-run metric values separately when more precision matters.
349`evaluate(testing=False)` references an undefined `true` variable in 1.1.15; use
350an explicit `Evaluator` on validation labels instead.
352The input is a list of per-run dictionaries, not `{seed: predictions}` and not a
353benchmark object indexed by seed. Non-docking groups require at least five runs.
354The 1.1.15 implementation returns a `ValueError` object instead of raising when
355fewer are supplied; the bundled CLI validates count first.
357The official guidance calls for at least five independent runs. A seed should
358control model initialization, stochastic training, and the train/validation split
359where the upstream splitter actually uses it. Report every seed and protocol.

Multi-run aggregation

prediction_runs = [
    {name: y_pred_seed_1},
    {name: y_pred_seed_2},
    {name: y_pred_seed_3},
    {name: y_pred_seed_4},
    {name: y_pred_seed_5},
]
summary = group.evaluate_many(prediction_runs)
# {normalized_name: [mean, population_standard_deviation]}

Single-run scores are rounded to three decimals before evaluate_many computes population standard deviation (ddof=0), then rounds the aggregate to three decimals. Retain raw per-run metric values separately when more precision matters. evaluate(testing=False) references an undefined true variable in 1.1.15; use an explicit Evaluator on validation labels instead.

The input is a list of per-run dictionaries, not {seed: predictions} and not a benchmark object indexed by seed. Non-docking groups require at least five runs. The 1.1.15 implementation returns a ValueError object instead of raising when fewer are supplied; the bundled CLI validates count first.

The official guidance calls for at least five independent runs. A seed should control model initialization, stochastic training, and the train/validation split where the upstream splitter actually uses it. Report every seed and protocol.

Raw lines 332 to 360
361## Bundled benchmark JSON
363Plan mode never constructs a group:
365```bash
366python scripts/benchmark_evaluation.py \
367 --group admet_group --dataset Caco2_Wang
368```
370Single-run input:
372```json
374 "caco2_wang": [0.1, 0.2, 0.3]
376```
378Multi-run input:
380```json
382 "runs": [
383 {"seed": 1, "predictions": {"caco2_wang": [0.1, 0.2]}},
384 {"seed": 2, "predictions": {"caco2_wang": [0.1, 0.2]}},
385 {"seed": 3, "predictions": {"caco2_wang": [0.1, 0.2]}},
386 {"seed": 4, "predictions": {"caco2_wang": [0.1, 0.2]}},
387 {"seed": 5, "predictions": {"caco2_wang": [0.1, 0.2]}}
388 ]
390```
392The CLI bounds input size/run count/value count, rejects non-finite numbers,
393duplicate normalized names, unequal benchmark sets or per-benchmark run lengths,
394and verifies test-row counts before scoring. It requires `--execute` before group
395construction. It intentionally excludes
396`docking_group` because that path can invoke docking, receptor downloads, molecular
397filters, and optional external services.

Bundled benchmark JSON

Plan mode never constructs a group:

python scripts/benchmark_evaluation.py \
  --group admet_group --dataset Caco2_Wang

Single-run input:

{
  "caco2_wang": [0.1, 0.2, 0.3]
}

Multi-run input:

{
  "runs": [
    {"seed": 1, "predictions": {"caco2_wang": [0.1, 0.2]}},
    {"seed": 2, "predictions": {"caco2_wang": [0.1, 0.2]}},
    {"seed": 3, "predictions": {"caco2_wang": [0.1, 0.2]}},
    {"seed": 4, "predictions": {"caco2_wang": [0.1, 0.2]}},
    {"seed": 5, "predictions": {"caco2_wang": [0.1, 0.2]}}
  ]
}

The CLI bounds input size/run count/value count, rejects non-finite numbers, duplicate normalized names, unequal benchmark sets or per-benchmark run lengths, and verifies test-row counts before scoring. It requires --execute before group construction. It intentionally excludes docking_group because that path can invoke docking, receptor downloads, molecular filters, and optional external services.

Raw lines 361 to 397
Runs code python scripts/benchmark_evaluation.py :366