Changelog
Pre-1.0 Compatibility Policy
RetroCast is still pre-1.0.0. Until 1.0.0, we will make breaking changes to the core schema or workflows whenever we see a compelling reason to do so. We'll try to keep some sane deprecation schedule unless it incurs a significant complexity cost.
Practically speaking, you should treat 2-raw (or wherever you store the raw planner outputs) as the source of truth and 3-processed, 4-scored, 5-results as regenerable.
For most use cases, this should not be a problem since complete evaluation through ingest, score, and analyze is decently fast. If you are using (or planning to use) RetroCast for large evaluation runs, please feel free to get in touch.
v0.8.2 (unreleased)
v0.8.2 adds the Rust-backed Python surface needed by external planner runners. Python can now load and validate tasks and stocks, write deterministic artifacts and execution statistics, and create or verify manifests without calling string-based binding plumbing or recreating RetroCast schemas.
The wheel remains a direct PyO3 binding with no Python implementation or runtime dependencies. A bundled type stub and py.typed marker describe the curated public API.
Task loading now has an explicit trust boundary: normal reads validate structure without recalculating chemistry, while validate_task and write_task reject invalid SMILES and SMILES/InChIKey mismatches. Runners can ask RetroCast to resolve each target's effective stock requirement without reproducing constraint override rules.
Planner manifests now require existing artifacts and can hash raw outputs from disk without serializing their full values across the Python/Rust boundary. Verification is strict by default, and the planner-specific creator and verifier enforce the adapter and safe raw-results directive contract consumed by project ingest. Producer schema errors surface as ValueError; filesystem failures surface as OSError.
Reaction provenance now distinguishes source-reported templates from concrete atom-mapped reactions. Template-based adapters populate template only from an explicit planner template, while mapped_reaction_smiles stores the full mapped reaction in forward reactants>>product direction; reaction names and rendered actions remain annotations.
v0.8.1
v0.8.1 keeps corpus-sized artifacts inside Rust across ingest, score, and analyze. It also narrows ASKCOS pathway graphs before route casting and exposes --workers consistently across project commands.
The standalone evaluate command now processes adaptation, scoring, artifact writing, and metric preparation per target. Candidate and evaluation fragments are assembled in deterministic target order, while analysis retains compact scalar contributions instead of every scored route.
In single eight-worker measurements, standalone evaluation processed the 160-target, 25,762-candidate ASKCOS fixture in 13.65 seconds with 276 MiB peak RSS and the 400-target, 3,589-candidate AiZynthFinder fixture in 1.98 seconds with 61 MiB peak RSS. Candidate, evaluation, and analysis values matched the materialized implementation exactly.
v0.8.0
v0.8.0 replaces the Python execution engine with one Rust core shared by the Python package and a standalone retrocast executable.
Highlights
- Ported schema validation, every built-in planner adapter, route operations, ingest, scoring, analysis, statistics, artifact IO, provenance, datasets, curation, and training-release workflows to
retrocast-core. - Added a standalone executable with direct-file and project-mode
adapt,collect,ingest,score,analyze,evaluate,verify, and dataset commands. - Added PyO3 bindings behind the existing
import retrocastinterface. Untouched ingest and evaluation values remain Rust-owned between stages without intermediate JSON serialization. - Replaced Python RDKit with a narrow RDKit C++ bridge for canonical SMILES, InChIKeys, and molecular descriptors.
- Added bounded native parallelism through
workers, with the Python binding releasing the GIL while the core executes. - Added reproducible cross-platform wheel and standalone-bundle builds for Linux x86-64, Windows x86-64, macOS arm64, and macOS x86-64.
Distribution and migration
pip install retrocast now installs a native wheel containing the PyO3 extension and repaired RDKit libraries. It no longer installs the Python rdkit package. Source installations require Rust, a C++20 compiler, Boost headers, and RDKit C++.
Standalone archives contain the retrocast executable and its native libraries and do not require Python or Conda at runtime. This native packaging change is the reason for the minor-version bump.
v0.7.0
v0.7.0 updates RetroCast to new schema design, check that page for the full mental model.
Highlights
- Promoted the schema-2 models, adapters, workflow, metrics, IO, and CLI into the main
retrocastpackage. - Replaced the old prediction-wrapper path with
Candidate, which preserves either a successfulRouteor aFailureRecordfor benchmark accounting. - Split the workflow around the schema-2 path:
adapt -> collect -> score -> analyze. - Kept route-local node ids derived from tree position rather than serialized into
MoleculeorReactionobjects. - Added route signatures for full-route, prefix-depth, reaction, and subtree comparison.
- Implemented Solv-N scoring: Tier-N route validity plus task-constraint satisfaction.
Migration Notes
This is a hard pre-1.0 schema break. Processed, scored, and result artifacts produced by older schema-1 workflows should be regenerated from raw planner payloads.
v0.6.0
v0.6.0 makes RetroCast more useful as a general route-standardization library, not only as a benchmark runner. The main design change is the split between adapting provider output into canonical predictions and collecting those predictions onto benchmark targets for scoring.
For the full machine-generated change list, see the GitHub comparison after the release tag is published: v0.5.3...v0.6.0.
Highlights
- Added
PredictedRoute, an envelope around canonicalRoutechemistry for provider-level rank, score, confidence, source row, and metadata. - Split adaptation from benchmark collection with
adapt_provider_output(...),adapt_target_keyed_provider_output(...), andcollect_benchmark_predictions(...). - Kept
retrocast ingestas the project-mode convenience command for the common raw-output-to-benchmark-routes workflow. - Added route-corpus IO and CLI support for streamable prediction artifacts such as
.jsonl.gz. - Added support for flat LLM completion corpora with
<synthesis_step>XML blocks. - Added new adapter coverage, including MolBuilder, and renamed public adapter classes/slugs toward canonical planner names.
- Added stable error codes for adapter, IO, CLI, and workflow boundaries, with ingest failure counts persisted into manifests.
- Added PaRoutes training-set release utilities and hosted dataset loaders.
- Improved release packaging so PyPI releases build from the release tag, support intentional dev releases, and use Hatchling/Hatch VCS for dynamic versions.
Adapter Workflow Split
Earlier versions centered the public workflow on ingest, which did two jobs at once:
- standardize raw planner output into canonical
Routeobjects - collect those routes onto benchmark targets and write
routes.json.gz
That shape worked for benchmark runs, but it made RetroCast feel less like a general route-standardization library. In v0.6.0, standardization and benchmark collection are separate library workflows. ingest still exists as the one-command wrapper that runs both.
| Before v0.6.0 | v0.6.0 replacement | Why |
|---|---|---|
adapt_single_route(raw, target, adapter_name) |
adapt_route(raw_route, adapter) |
The one-route API no longer requires target context when the raw route carries its own target. |
adapt_routes(raw, target, adapter_name) |
adapt_provider_output(raw_provider_output, adapter) |
Standardization can now handle one provider output without benchmark target context. |
retrocast ingest as the main way to adapt benchmark predictions |
retrocast adapt then retrocast collect for ad-hoc use |
Exposes standardization and benchmark collection as separate steps. |
retrocast ingest for project-mode benchmark runs |
still retrocast ingest |
Project mode keeps the one-command convenience wrapper. |
Route.rank |
PredictedRoute.rank during provider-output adaptation, list order in scoring inputs |
Keeps canonical Route free of benchmark/list-position metadata while preserving provider rank. |
Provider-output adaptation APIs now return PredictedRoute, an envelope around canonical Route chemistry. It carries provider-level metadata such as rank, score, confidence, and source row provenance. adapt_route(...) remains the single-payload chemistry API and returns Route | None; use adapt_prediction(...) when you explicitly want a one-off prediction envelope.
Scoring artifacts remain benchmark-keyed dict[target_id, list[Route]] so existing evaluation semantics stay stable. Prediction manifest content_hash values are now order-sensitive within each target because route list order is the ranking signal. Re-exported manifests can therefore differ from older manifests even when the route structures are otherwise identical.
Deprecations
The v0.6.0 compatibility layer intentionally warns instead of removing the old names immediately. These surfaces are scheduled for removal in v0.9.0:
- legacy adapter slugs such as
aizynth,dms, anddreamretro - legacy adapter class aliases such as
AizynthAdapter,DMSAdapter, andDreamRetroAdapter - target-local adaptation helpers
adapt_single_route(...)andadapt_routes(...)
See the deprecation schedule for the canonical removal plan.
Migration
For one raw route-like payload:
from retrocast import adapt_route
from retrocast.adapters import DirectMultiStepAdapter
adapter = DirectMultiStepAdapter()
route = adapt_route(raw_route, adapter)
For one raw prediction payload where you want an envelope:
from retrocast import adapt_prediction
from retrocast.adapters import DirectMultiStepAdapter
adapter = DirectMultiStepAdapter()
prediction = adapt_prediction(raw_route, adapter, rank=1)
For one raw provider output containing one or many routes:
from retrocast import adapt_provider_output
from retrocast.adapters import AiZynthFinderAdapter
adapter = AiZynthFinderAdapter()
predictions = adapt_provider_output(raw_provider_output, adapter)
For raw output already keyed by target ID or target SMILES:
from retrocast import adapt_target_keyed_provider_output, load_benchmark
from retrocast.adapters import AiZynthFinderAdapter
benchmark = load_benchmark("benchmark.json.gz")
adapter = AiZynthFinderAdapter()
predictions = adapt_target_keyed_provider_output(raw_mapping, benchmark, adapter)
To produce benchmark-keyed routes for scoring:
from retrocast import collect_benchmark_predictions
collected = collect_benchmark_predictions(predictions, benchmark)
routes_by_target = collected.routes_by_target
Ad-hoc CLI users can now run the two steps explicitly:
retrocast adapt \
--input raw_predictions.json.gz \
--adapter aizynthfinder \
--input-kind provider-output \
--output route-corpus.jsonl.gz
retrocast collect \
--input route-corpus.jsonl.gz \
--benchmark benchmark.json.gz \
--output routes.json.gz
Project-mode users can keep using retrocast ingest.