reactionstudio-python
Python client for ReactionStudio — run conformer searches, geometry optimisations and transition-path calculations from code.
pip install reactionstudio
Getting a key
Sign in to ReactionStudio, go to the compute platform, open Account → API keys, and generate one. The key is shown once. Put it in your environment:
export REACTIONSTUDIO_API_KEY=rs_live_...
A key can submit jobs and read their status and results. It cannot buy tokens, create further keys, or read your billing history — those need a browser session, so a leaked key can't escalate.
Quick start
import reactionstudio as rs
client = rs.Client()
conformers = client.generate_conformers( "CCO", force_field="gfn2-xtb" ).wait()
print(f"{len(conformers)} conformers")
print(f"lowest energy: {conformers.lowest_energy.energy:.2f} kJ/mol")
wait() blocks until the job finishes, polling with backoff, and raises if the
computation fails.
The three experiments
Conformer generation
Takes a SMILES string, no chemistry packages required.
conformers = client.generate_conformers(
"CC(=O)Oc1ccccc1C(=O)O",
force_field="gfn2-xtb",
n_initial_conformers=1000, # embedded before pruning
).wait()
for c in conformers:
print( c.energy, c.cluster_size )
best = conformers.molecule(0) # reusable as input elsewhere
Geometry optimisation
mol = rs.Molecule.from_file( "ethanol.xyz" ) # Will infer bonds from atom distances.
result = client.stabilize_conformation( mol, force_field="gfn2-xtb" ).wait()
print(result.converged, result.final_energy)
print(result.energy_change) # how far downhill it went, kJ/mol
Transition paths
path = client.transition_path(
rs.Molecule.from_file("reactant.xyz"),
rs.Molecule.from_file("product.xyz"), # should contain atoms in the same order
force_field="gfn2-xtb",
n_images=20,
).wait()
print(f"barrier: {path.barrier:.1f} kJ/mol")
print(f"reaction energy: {path.reaction_energy:.1f} kJ/mol")
open("path.xyz", "w").write(path.to_xyz()) # multi-frame trajectory
ts = path.transition_state # a Molecule
Both structures must have their atoms in the same order — interpolation is per-atom, so the client checks and refuses a mismatch rather than letting you pay for a meaningless path.
Molecules
rs.Molecule.from_file("thing.xyz") # .xyz, .mol, .sdf natively; .pdb via ase
rs.Molecule.from_xyz(text)
rs.Molecule.from_molfile(text) # keeps the bond block
rs.Molecule.from_smiles("CCO") # needs [rdkit]; does not use the API
rs.Molecule.from_ase(atoms) # needs [ase]
rs.Molecule.from_rdkit(mol) # needs [rdkit]
rs.Molecule.new([6, 8], [[0, 0, 0], [1.2, 0, 0]])
new() accepts element symbols instead of atomic numbers, and numpy arrays
anywhere coordinates are expected. Supplying bonds lets the backend skip its
own bond inference, which matters for .xyz input where connectivity is
otherwise guessed from interatomic distances.
Chemistry interop
The core client has one dependency, httpx. Conversions are optional extras:
pip install 'reactionstudio[ase]' # ASE
pip install 'reactionstudio[rdkit]' # RDKit
pip install 'reactionstudio[all]'
atoms = result.to_ase() # Atoms
images = path.to_ase() # list[Atoms], write with ase.io.write
frames = conformers.to_ase() # list[Atoms], one per conformer
Long jobs
wait() is the common case. For anything long, drive the loop yourself:
job = client.transition_path(reactant, product, force_field="gfn2-xtb")
print("submitted", job.run_id)
try:
path = job.wait(timeout=3600, on_progress=lambda s: print(s.status, s.progress))
except rs.RunTimeoutError:
pass # the run continues server-side
Or hand it off to a thread and carry on:
future = job.wait_async() # concurrent.futures.Future
...
result = future.result() # blocks only when you actually need it
Submitting a batch? Wait on them together — the backend runs them concurrently, so waiting one at a time just wastes wall-clock:
jobs = [client.generate_conformers(s, force_field="gfn2-xtb") for s in smiles]
results = rs.wait_all(jobs) # submission order
results = rs.wait_all(jobs, raise_on_failure=False) # exceptions in place of results
A run_id is all you need to pick a job back up in another process. wait()
returns immediately for a run that has already finished:
job = client.job("6f2a...")
result = job.wait()
Downloads
Jobs produce files alongside their structured results:
job.download("out/") # server picks the filename
job.download("conformers.zip") # or specify it
job.download("in.mol", artifact_type="input_molecule")
artifact_type defaults to the experiment's main output — the optimised
.mol, the conformer .zip, or the transition-path .xyz. Inputs are kept
too, under input_molecule, reactant_input and product_input.
Tokens
Each run costs tokens, and submitting without enough raises before any compute starts:
print(client.token_balance())
try:
job = client.transition_path(r, p, force_field="gfn2-xtb")
except rs.InsufficientTokensError as exc:
print(f"needs {exc.required_tokens}, have {exc.current_balance}")
Force fields
for ff in client.force_fields(available_only=True):
print(ff.id, ff.supports_transition_path)
force_field is required on every submission and has no default — picking one
silently would be a good way to run a batch at the wrong level of theory. Not
every method supports transition paths.
Errors
All of these subclass rs.ReactionStudioError.
| Exception | Meaning |
|---|---|
AuthenticationError |
key missing, revoked, or expired |
InsufficientTokensError |
not enough tokens; carries the numbers |
NotFoundError |
no such run, or it isn't yours |
RunFailedError |
the computation failed; carries the backend's message |
RunTimeoutError |
wait() gave up; the run continues |
APIError |
unexpected HTTP response |
MissingDependencyError |
an extra is needed, e.g. [rdkit] |
Reads are retried on transient failures. Submissions are never retried — the API has no idempotency key, so a retry could create a second run and spend a second lot of tokens.
Conventions
Energies are kJ/mol, coordinates Angström. Argument names match the HTTP API, so anything in the API docs maps onto a keyword here, and numeric defaults match the web app's, so a script and the UI give the same answer for the same input.
Development
pip install -e '.[test,all]'
pytest
Licence
Apache 2.0
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file reactionstudio-0.1.0.tar.gz.
File metadata
- Download URL: reactionstudio-0.1.0.tar.gz
- Upload date:
- Size: 39.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fb31acc4decbb9193aad48f8f194787b08db2e1e0d50228ff53717593309bdde
|
|
| MD5 |
556ee5e77c718abe9cb565ecbd30f122
|
|
| BLAKE2b-256 |
cf4856cef0af411f065df9ef8f026766677b70f7dd8394357fba04f0c8899b3f
|
File details
Details for the file reactionstudio-0.1.0-py3-none-any.whl.
File metadata
- Download URL: reactionstudio-0.1.0-py3-none-any.whl
- Upload date:
- Size: 32.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.12.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
02f74afc06684b627f8395b366189661442b543a6041a7aa15cf34bc7db8646b
|
|
| MD5 |
9230da76add2fb1f67d8dce3e8a0c4b1
|
|
| BLAKE2b-256 |
9feb462cd9e5addb85d88021b308911ffda8e0e5f27ac5c3fec393093d7d5fc2
|