xyzrecover
xyzrecover is a small RDKit-based package for recovering molecular identifiers from XYZ coordinate files when the bond graph and charge were lost.
It is designed for batches of XYZ files that may contain one molecule, multiple disconnected covalent molecules, or multi-frame XYZ blocks. It uses RDKit's native rdDetermineBonds / xyz2mol implementation to infer connectivity and bond orders, then emits canonical SMILES, explicit-H SMILES, formal charge, formula, InChI, and InChIKey.
Important limitation
XYZ files do not contain bond orders, formal charges, spin state, resonance choice, protonation intent, or non-covalent association metadata. For some geometries, more than one chemically valid assignment is possible. xyzrecover therefore searches candidate charges, scores the successful assignments, and includes alternates in the output instead of pretending the answer is always unique.
When you know the total charge, pass it. That is the single most useful constraint.
RDKit's valence model only handles main-group organic elements. Components
containing metals or noble gases are reported with status="unsupported" and
smiles=None rather than perceived, because RDKit would otherwise return a
silently wrong structure at high confidence (a lone Na⁺ comes back as [NaH]).
The supported set is DEFAULT_SUPPORTED_ELEMENTS; override it via
PerceptionConfig(supported_elements=...), or disable the check with
restrict_to_supported_elements=False (CLI: --no-element-check).
The one exception is ionic metal counterions: an isolated group 1 / group 2
metal atom (Na, K, Li, Ca, Mg, …) is recovered directly as its ion ([Na+],
[Ca+2]) using the group oxidation state, bypassing RDKit. Such atoms are split
out of any short ionic contact first, so a salt like Na⁺·methyl-phosphate⁻
recovers the metal as [Na+] and perceives the anion separately, and (when you
pass total_charge) the charge is balanced across the ion and the anion. This
is on by default; disable with recover_ionic_metals=False (CLI:
--no-ionic-metals). Transition metals and multi-atom metal fragments are still
reported unsupported.
Nitro groups are fixed before bond orders are assigned. Left to itself,
RDKit's xyz2mol often returns a nitro group as N([O-])[O-], offset by charges
it invents elsewhere ([O+], [S+]), or fails outright, depending on the atom
order. Any N with three bonds, two of them to terminal O atoms (nitro, nitrate,
nitramine), is therefore assigned [N+](=O)[O-], and RDKit perceives the rest
of the component with the group replaced by a neutral stand-in. Nitronate
anions come out as the carbanion resonance form ([CH2-][N+](=O)[O-]), which
has the same InChI. This is on by default (only with charged fragments, not
--radicals-instead-of-charges); disable with fix_nitro_groups=False (CLI:
--no-nitro-fix).
Install
python -m pip install rdkit
python -m pip install -e .
Conda also works well for RDKit:
conda install -c conda-forge rdkit
python -m pip install -e .
The package installs a xyzrecover console script; you can also run it as a
module with python -m xyzrecover.
Charge options apply to every block.
--total-chargeand--per-fragment-chargesare applied to every XYZ block in every input file. Use one charge state per invocation for heterogeneous batches.
CLI examples
Unknown charge, try charge states -3 through +3:
xyzrecover molecule.xyz --json results.json --csv results.csv --sdf results.sdf
Known total charge for each XYZ block:
xyzrecover acetate.xyz --total-charge -1
A salt or cluster with multiple disconnected components and known total charge:
xyzrecover ion_pair.xyz --total-charge 0 --candidate-charges=-2,-1,0,1,2
Known per-component charges. The order follows connectivity perception (the
same order as molecule_index in the output, not the input atom order), so
run once unconstrained first to see how the structure splits:
xyzrecover fragments.xyz --per-fragment-charges=1,-1,0
If an asserted charge can't be satisfied for a component, fall back to the candidate charge search for that component instead of failing it:
xyzrecover molecule.xyz --total-charge 2 --charge-fallback
Use extended Hueckel connectivity if your RDKit build supports YAeHMOP:
xyzrecover molecule.xyz --use-hueckel --total-charge 1
Python API
from xyzrecover import PerceptionConfig, recover_xyz_file, records_to_json
config = PerceptionConfig(
total_charge=-1, # optional but recommended when known
candidate_charges=(-2, -1, 0, 1), # used when fragment charge is unknown
split_fragments=True,
)
records = recover_xyz_file("input.xyz", config)
for rec in records:
print(rec.molecule_index, rec.formula, rec.charge, rec.smiles, rec.inchikey, rec.confidence)
print(records_to_json(records))
Each record contains:
status:ok,failed(RDKit could not assign bonds), orunsupported(component contains an element RDKit cannot handle)charge: RDKit formal charge for the selected assignmentsmiles: canonical SMILES, with hydrogens made implicit when possibleexplicit_h_smiles: canonical SMILES retaining explicit hydrogens from the XYZinchiandinchikeyconfidence:high,medium,low, ornoneblock_index/molecule_index: which XYZ block (frame) and which recovered component the record came fromatom_indices: indices into the source block's atoms for this componentalternates: other successful charge/bond-order assignments, when presentwarnings/errors: notes and failed charge attempts, useful for debugging
Multi-frame XYZ
Each XYZ block is processed independently, so a multi-frame file (e.g. a
trajectory of one molecule) yields one set of records per frame, distinguished
by block_index. De-duplicate on inchikey downstream if you only want unique
species.
Recommended workflow
- Run with known total charge whenever available.
- Inspect any record with
confidence != "high"or non-emptyalternates. - For unusual bonding, radicals, metals, transition states, hypervalent species, or very close non-covalent contacts, validate against quantum chemistry output or another bond-perception method.
- Keep the SDF output because it preserves the recovered 3D coordinates and the inferred bond graph.
What to expect (benchmarks)
Recovery rate and confidence depend strongly on the chemistry. Measured across several datasets (COMP6, ANI-1x/ANI-1xm, GDB, peptides, H-bonded and ion-pair complexes):
| Regime | Perceived | Typical confidence | How to handle |
|---|---|---|---|
| Neutral organics (COMP6, ANI-1xm, GDB, peptides) | 98–100% | high |
Auto-accept. Averaging over multiple conformers recovers strained cases (→99.8%). |
| Neutral H-bonded complexes | ~100% (matches OpenBabel) | high |
Use split_fragments=True (default). |
| Proton-transfer / ion-pair complexes | recovered | medium |
Genuinely ambiguous (where is the proton?); ≥ OpenBabel. Gate on confidence and inspect alternates. |
| Charged organic ions, charge unknown | ~86% | lower | Declare the charge (--total-charge) to push to high; otherwise gate. |
| Group 1/2 metal counterions (Na, K, Li, Ca, Mg) | lone ions recovered | high |
Recovered as [Na+]/[Ca+2] via the ionic fast-path; pass total_charge to balance the anion. |
| Transition metals / multi-atom metal fragments | not supported | none |
Reported as status="unsupported", smiles=None. Use a metal-aware tool (e.g. xyz2mol_tm, xtb WBO). |
Practical gating rule: auto-accept status="ok" with confidence="high" and
empty alternates; route everything else (medium/low, non-empty
alternates, failed, unsupported) to review or a second method.
Development
Install the dev extras (pytest, ruff, mypy) and run the checks:
python -m pip install -e ".[dev]"
python -m pytest # tests
ruff check . # lint
ruff format . # format
CI runs ruff (lint + format check) and pytest on Python 3.10–3.12.
License
MIT — see LICENSE.
Metadata
Release files for xyzrecover 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| xyzrecover-0.2.0.tar.gz | 30.6 kB | With provenance | Details for xyzrecover-0.2.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| xyzrecover-0.2.0-py3-none-any.whl | Python 3 | none | any | Details for xyzrecover-0.2.0-py3-none-any.whl |
Total release size: 51.5 kB
Release files / xyzrecover-0.2.0.tar.gz
| Download URL | xyzrecover-0.2.0.tar.gz |
|---|---|
| Size | 30.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1317b8f2d4fe0455192dd158a07962d12f15e3362baca7a46c306ab098752764
|
|
BLAKE2b-256 checksum How to use checksums |
d5231b4650d37f8d84627e641d2d8556c23275dd0d96f0d2d9d33548d4ce933a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.
Transparency logRelease files / xyzrecover-0.2.0-py3-none-any.whl
| Download URL | xyzrecover-0.2.0-py3-none-any.whl |
|---|---|
| Size | 20.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
9803bdb2a423ca3b415eb57b3db035d1a8e8edb3d8ab23697a60c854f4b653dd
|
|
BLAKE2b-256 checksum How to use checksums |
6fb2c0aa1090e66bc79d732c611979f80baf3c9952ef049e065f0581551f788a
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.
Transparency logSecurity for xyzrecover 0.2.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
Full provenance
All 2 files in this release have been uploaded with provenance metadata.
Attestation for xyzrecover-0.2.0.tar.gz
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 20, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.