Skip to main content

OEFP

High-performance molecular fingerprints for the OpenEye Toolkits.

OEFP generates RDKit-compatible Morgan and topological Atom Pair fingerprints from OpenEye molecules, stores them in compact C++ containers, and compares them with fast scalar and batch kernels. It also provides Morgan and topological Atom Pair descriptor rows plus Mordred-compatible and RDKit-compatible descriptors.

OEFP currently supports dense binary, sparse binary, and sparse counted fingerprint containers; scalar comparison; query-to-batch comparison; cdist; SciPy-compatible condensed pdist; and Arrow/Parquet interchange for schema-backed descriptor rows.

Try it out:

pip install oefp

Usage

Here are a few examples of using oefp.

Python

from openeye import oechem
import oefp

mol = oechem.OEGraphMol()
oechem.OESmilesToMol(mol, "CC(=O)OC1=CC=CC=C1C(=O)O")  # aspirin

# Generate an RDKit-compatible Morgan fingerprint.
fp = oefp.morgan_fingerprint(mol, radius=2, num_bits=2048)
print(fp.popcount)
print(fp.words[:4])

# Compare fingerprints.
score = oefp.compare(fp, fp, oefp.Metric.tanimoto())
print(score)

Use reusable generators when applying the same options to many molecules:

from openeye import oechem
import oefp

smiles = ["c1ccccc1", "c1ccc(O)cc1", "CC(=O)O"]
mols = []
for smi in smiles:
    mol = oechem.OEGraphMol()
    oechem.OESmilesToMol(mol, smi)
    mols.append(mol)

generator = oefp.MorganGenerator(radius=2, num_bits=2048)
fps = [generator.fingerprint(mol) for mol in mols]

batch = oefp.OEFPBatch.from_fingerprints(fps)
distances = oefp.pdist(batch, oefp.Metric.jaccard())

Build a batch directly from molecules in one call:

batch = oefp.OEFPBatch.from_molecules(mols, oefp.morgan_fingerprint, radius=2)
distances = oefp.pdist(batch, oefp.Metric.jaccard())

from_molecules is available on OEFPBatch, OEFPCountBatch, OEFPSparseBatch, and DescriptorBatch; pass the matching generator (morgan_count_fingerprint, morgan_sparse_fingerprint, morgan_descriptors, the Atom Pair / Topological Torsions functions, …) and any keyword options.

Generate sparse and counted fingerprints:

folded_count = oefp.morgan_count_fingerprint(mol)
sparse_binary = oefp.morgan_sparse_fingerprint(mol)
atom_pair_count = oefp.atom_pair_sparse_count_fingerprint(mol)

print(folded_count.indices[:5])
print(folded_count.counts[:5])
print(sparse_binary.indices[:5])
print(atom_pair_count.total_count)

Inspect Morgan bit provenance:

result = oefp.morgan_fingerprint_with_mapping(mol)
print(result.fingerprint.popcount)
print(result.mapping.bit_info())

Import and export OpenEye fingerprints:

from openeye import oechem, oegraphsim
import oefp

mol = oechem.OEGraphMol()
oechem.OESmilesToMol(mol, "CCO")

oe_fp = oegraphsim.OEFingerPrint()
oegraphsim.OEMakeCircularFP(oe_fp, mol)

fp = oefp.from_openeye_fingerprint(oe_fp)
round_tripped = oefp.to_openeye_fingerprint(fp)
print(oegraphsim.OETanimoto(oe_fp, round_tripped))

Work with Mordred-compatible named descriptors:

from openeye import oechem
import oefp

mol = oechem.OEGraphMol()
oechem.OESmilesToMol(mol, "CCO")

row = oefp.mordred_descriptors(mol)
schema = row.schema

print(schema.schema_id)
print(row["MW"])
print(row["GeomDiameter"])  # None unless the input already has 3D coordinates.

requires_3d = [
    definition.name
    for definition in schema.definitions
    if definition.prerequisites & oefp.DESCRIPTOR_PREREQUISITE_COORDINATES_3D
]
print(len(requires_3d))

Descriptor calculation never generates 2D or 3D coordinates implicitly. When a descriptor requires coordinates that the input molecule does not already have, that descriptor value remains missing (None in Python).

Work with RDKit-compatible named descriptors:

from openeye import oechem
import oefp

mol = oechem.OEGraphMol()
oechem.OESmilesToMol(mol, "CC(=O)OC1=CC=CC=C1C(=O)O")  # aspirin

row = oefp.rdkit_descriptors(mol)
schema = row.schema

print(len(schema.names))        # 213 native RDKit 2D descriptors
print(row["MolWt"])             # 180.157...
print(row["TPSA"])              # 63.6
print(row["NumAromaticRings"])  # 1
print(row["MolLogP"])           # Wildman-Crippen SLogP
print(row["qed"])               # 0.550...

OEFP reproduces 213 of RDKit's 2D descriptors natively, matched to RDKit 2026.03.3 within per-descriptor tolerance tiers. RDKit is used only as a test-time conformance oracle; it is not a runtime dependency. Four of RDKit's 217 descriptors are excluded from the schema: three structurally-always-zero VSA bins (SMR_VSA8, SlogP_VSA9, EState_VSA11) and SPS (SpacialScore), whose exact value depends on RDKit's aromaticity perception, which differs from OpenEye's on some conjugated ring systems.

Use RDKitDescriptorSource in a calculator to compute batches or select a subset of columns:

calc = oefp.DescriptorCalculator([oefp.RDKitDescriptorSource()])

smiles = ["c1ccccc1", "c1ccc(O)cc1", "CC(=O)O"]
mols = []
for smi in smiles:
    m = oechem.OEGraphMol()
    oechem.OESmilesToMol(m, smi)
    mols.append(m)

batch = calc.calculate_batch(mols)
print(batch.size)               # 3
print(batch[0]["MolLogP"], batch[0]["NumHAcceptors"])

The Mordred and RDKit sources share many descriptor names (for example BalabanJ, Chi0, and TPSA). Registering both in one calculator without narrowing raises a name-collision error, since the same name would appear twice. Select the columns you want from one source to combine them:

calc = oefp.DescriptorCalculator([
    oefp.OpenEyePropertyDescriptorSource(),
    (oefp.RDKitDescriptorSource(), ["MolLogP", "TPSA", "qed"]),
])
print("MolLogP" in calc.schema.names)  # True

Compute merged, deduplicated descriptors from multiple sources:

from openeye import oechem
import oefp

# Build a descriptor calculator from Mordred and OpenEye property sources.
calc = oefp.DescriptorCalculator([
    oefp.MordredDescriptorSource(),
    oefp.OpenEyePropertyDescriptorSource(),
])

# The schema deduplicates by canonical_id with first-wins by registration
# order. Mordred is registered first, so its MW and nHBAcc are kept and
# OpenEye's MolecularWeight and HBA are dropped (same canonical_id). XLogP
# survives because it has no Mordred equivalent.
print(len(calc.schema.names))
print("MW" in calc.schema.names)         # True (Mordred kept)
print("MolecularWeight" in calc.schema.names)  # False (OpenEye duplicate dropped)
print("HBA" in calc.schema.names)        # False (dedup: Mordred nHBAcc wins)
print("XLogP" in calc.schema.names)      # True (OpenEye-unique survives)

# Build molecules.
smiles = ["c1ccccc1", "c1ccc(O)cc1", "CC(=O)O"]
mols = []
for smi in smiles:
    mol = oechem.OEGraphMol()
    oechem.OESmilesToMol(mol, smi)
    mols.append(mol)

# Compute a batch.
batch = calc.calculate_batch(mols)
print(batch.size)
print(list(batch.schema.names)[:5])

Work with DescriptorBatch sugar:

# Iterate rows as {name: value} dictionaries.
for row in batch:
    print(row["MW"], row["XLogP"])

# Or get the first row.
print(batch[0]["MW"])

# Convert to column-oriented form.
columns = batch.to_dict()
print(columns["MW"])  # [value1, value2, value3, ...]

# Convert to row-oriented list of dicts.
rows = batch.to_records()
print(rows[0])

# Vertically concatenate two batches with the same schema.
combined = batch + batch
print(combined.size)

Work with kallisto atom and bond descriptors:

from openeye import oechem
import oefp

# Load a molecule with pre-existing 3D coordinates.
mol = oechem.OEGraphMol()
ifs = oechem.oemolistream("tests/data/kallisto_panel/ethane.sdf")
oechem.OEReadMolecule(ifs, mol)

# Compute per-atom descriptors: coordination numbers (cn_erf, cn_cov, cn_exp),
# proximity (prox), EEQ partial charge (eeq), dynamic polarizability (alp),
# and van der Waals radii (vdw_rahm, vdw_truhlar).
atom_result = oefp.kallisto_atom_descriptors(mol)
print(atom_result.atom_count)  # 8 atoms in ethane
print(atom_result["eeq"])  # Array of EEQ partial charges in atomic units (e)
print(atom_result["cn_erf"])  # erf coordination numbers (dimensionless)
print(atom_result.eeq)  # Column access via attribute

# Compute per-bond descriptors: Sterimol L/B1/B5 (bond length and cross-sections).
bond_result = oefp.kallisto_bond_descriptors(mol)
print(bond_result.bond_count)  # 14 directed bonds in ethane
print(bond_result["sterimol_L"])  # Sterimol L values in Bohr

# Access schema information.
atom_schema = oefp.kallisto_atom_schema()
bond_schema = oefp.kallisto_bond_schema()
print(atom_schema.names)  # ('cn_erf', 'cn_cov', 'cn_exp', 'prox', 'eeq', 'alp', 'vdw_rahm', 'vdw_truhlar')
print(bond_schema.names)  # ('sterimol_L', 'sterimol_B1', 'sterimol_B5')

# Compute Sterimol for a specific bond (origin and partner are atom indices).
origin = 0  # First carbon in ethane
partner = 1  # Second carbon in ethane
L, B1, B5 = oefp.sterimol(mol, origin, partner)
print(f"Sterimol L={L:.3f} B1={B1:.3f} B5={B5:.3f}")  # Values in Bohr

# Batch compute for multiple molecules.
mols = []
for sdf_name in ["methane.sdf", "ethane.sdf", "methanethiol.sdf"]:
    m = oechem.OEGraphMol()
    ifs = oechem.oemolistream(f"tests/data/kallisto_panel/{sdf_name}")
    oechem.OEReadMolecule(ifs, m)
    mols.append(m)

atom_batch = oefp.kallisto_atom_descriptors_batch(mols)
bond_batch = oefp.kallisto_bond_descriptors_batch(mols)
print(len(atom_batch))  # 3 segments (one per molecule)
print(len(atom_batch[0]["eeq"]))  # 5 atoms in methane
print(len(bond_batch[1]["sterimol_L"]))  # 14 directed bonds in ethane

Kallisto atom and bond descriptors require molecules with pre-existing 3D coordinates. OEFP never generates coordinates; if a molecule lacks 3D coordinates or contains any atom with atomic number greater than 86, that molecule is skipped and yields an empty result for both atom and bond descriptors.

All descriptor values are returned in kallisto's native atomic units:

  • Coordination numbers (cn_erf, cn_cov, cn_exp) and proximity (prox) are dimensionless
  • EEQ partial charges (eeq) are in elementary charge units (e)
  • Dynamic polarizabilities (alp) are in cubic Bohr (Bohr^3)
  • van der Waals radii (vdw_rahm, vdw_truhlar) and Sterimol parameters (L, B1, B5) are in Bohr

The kallisto port reproduces kallisto 1.0.10 parameter tables and numeric methods. kallisto is used as a test-time conformance oracle only and is not a runtime dependency of OEFP. For more information about kallisto, see https://github.com/AstraZeneca/kallisto

C++

#include <oefp/oefp.h>
#include <oechem.h>
#include <iostream>

int main() {
    OEChem::OEGraphMol mol_a;
    OEChem::OEGraphMol mol_b;
    OEChem::OESmilesToMol(mol_a, "c1ccccc1");
    OEChem::OESmilesToMol(mol_b, "c1ccc(O)cc1");

    OEFP::MorganGenerator generator;
    OEFP::OEFP fp_a = generator.Fingerprint(mol_a);
    OEFP::OEFP fp_b = generator.Fingerprint(mol_b);

    double score = OEFP::Compare(fp_a, fp_b, OEFP::Metric::Tanimoto());
    std::cout << score << "\n";

    return 0;
}

Supported Fingerprints

Family Outputs Notes
Morgan Folded binary, folded count, sparse binary, sparse count Bit mapping is available for all Morgan outputs
Topological Atom Pair Folded binary, folded count, sparse binary, sparse count Uses connectivity distances; legacy atom_pair_* names remain compatibility aliases
OpenEye OEFingerPrint import/export Numeric type metadata is preserved when available

Supported Descriptors

Family Output Notes
Morgan Raw counted integer-key descriptors Uses unfurled Morgan environment identifiers
Topological Atom Pair Raw counted string-key descriptors Uses graph shortest-path distances and requires no coordinate generation
Distance Atom Pair Reserved Requires existing 3D coordinates and is not implemented yet
Mordred-compatible Schema-backed named descriptor rows Full Mordred 1.2.0 schema with implemented values filled and unsupported or unavailable values left missing
RDKit-compatible Schema-backed named descriptor rows 213 of RDKit's 2D descriptors reproduced natively and matched to RDKit within tolerance
Kallisto atom and bond Per-atom and per-bond geometric descriptors Coordination numbers, EEQ partial charges, dynamic polarizabilities, van der Waals radii, and Sterimol parameters; requires pre-existing 3D coordinates

Morgan supports both ECFP-style and FCFP-style pharfingerprints via use_features=True (Donor, Acceptor, Aromatic, Halogen, Basic, Acidic), matching RDKit's feature. morgan_fingerprint(mol, radius=2) is ECFP4; morgan_fingerprint(mol, radius=2, use_features=True) is FCFP4.

Morgan, Topological Atom Pair, and Topological Torsions outputs support RDKit-compatible chirality encoding with use_chirality=True directly from OpenEye molecule objects. There may be some differences in chirality encoding between OpenEye and RDKit based on how each toolkit represents stereo information.

Installation

Install OpenEye Toolkits first:

pip install --extra-index-url https://pypi.anaconda.org/openeye/simple openeye-toolkits

Install OEFP:

pip install oefp

Build from Source

Set the OpenEye C++ SDK path:

export OPENEYE_ROOT=/path/to/openeye/sdk

Build the C++ library and Python bindings:

cmake --preset debug
cmake --build build-debug

Install the Python package in editable mode:

pip install --config-settings editable_mode=compat -e python/

The editable_mode=compat flag keeps the package on a traditional editable path that works with compiled SWIG extension modules.

Tests

C++ tests:

cmake --build build-debug --target oefp_tests
ctest --test-dir build-debug --output-on-failure

Python tests:

PYTHONPATH=python python -m pytest tests/python -q

RDKit and SciPy are required for conformance tests but are not runtime dependencies.

Documentation

Build the Sphinx documentation:

python -m pip install -r docs/requirements.txt
make -C docs html

Open the local build:

open docs/_build/html/index.html

The documentation includes installation, quickstart, Python API notes, C++ API reference generation through Doxygen, and release build guidance.

Benchmarks

Run the RDKit generation and dense pdist benchmark:

PYTHONPATH=python python benchmarks/benchmark_rdkit_generation.py \
  --max-mols 1500 \
  --trials 7 \
  --warmup 1 \
  --pdist-size 400 \
  --generation-max-ratio 1.10 \
  --atom-pair-generation-max-ratio 1.10

Run the optional C++ guardrail against a local oecluster checkout:

cmake -S . -B build-bench \
  -DOEFP_BUILD_BENCHMARKS=ON \
  -DOEFP_OECLUSTER_SOURCE_DIR=/path/to/oecluster
cmake --build build-bench --target oefp_oecluster_fingerprint_benchmark
./build-bench/benchmarks/oefp_oecluster_fingerprint_benchmark 512 0 256

Tools

Tool Purpose
CMake C++ build system
SWIG Python bindings
scikit-build-core Python wheel build backend
cmake-openeye OpenEye CMake discovery and SWIG helpers
vrzn Version synchronization
pytest Python tests
Sphinx Documentation

License

MIT

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

oefp-0.3.0-cp310-abi3-win_amd64.whl (17.9 MB view details)

Uploaded CPython 3.10+Windows x86-64

oefp-0.3.0-cp310-abi3-manylinux_2_34_aarch64.whl (2.2 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.34+ ARM64

oefp-0.3.0-cp310-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (2.3 MB view details)

Uploaded CPython 3.10+manylinux: glibc 2.24+ x86-64manylinux: glibc 2.28+ x86-64

oefp-0.3.0-cp310-abi3-macosx_26_0_arm64.whl (1.4 MB view details)

Uploaded CPython 3.10+macOS 26.0+ ARM64

File details

Details for the file oefp-0.3.0-cp310-abi3-win_amd64.whl.

File metadata

  • Download URL: oefp-0.3.0-cp310-abi3-win_amd64.whl
  • Upload date:
  • Size: 17.9 MB
  • Tags: CPython 3.10+, Windows x86-64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for oefp-0.3.0-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 7a9cfff9b281c9354d5937a88c37d63e1d98b1ecba7e3f9a84001deb5291603b
MD5 ffe8a2a1ae3eb371fce51dcd09f54c14
BLAKE2b-256 93e6bef4570ab40338438b450cb3e8f07e365ba0bd5dbcfaf1fa125af0b93636

See more details on using hashes here.

Provenance

The following attestation bundles were made for oefp-0.3.0-cp310-abi3-win_amd64.whl:

Publisher: build-wheels.yml on scott-arne/oefp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file oefp-0.3.0-cp310-abi3-manylinux_2_34_aarch64.whl.

File metadata

File hashes

Hashes for oefp-0.3.0-cp310-abi3-manylinux_2_34_aarch64.whl
Algorithm Hash digest
SHA256 685572da0685d7b57e22c8002504c3828f5c29f6250786176bd259e997414665
MD5 21498b4a834a6bf5a0bf2565a60db060
BLAKE2b-256 c5dcdc859f873e89b04ec6e051f41ccf3c1288ab1a3af90ee05a6d92ab9bbf96

See more details on using hashes here.

Provenance

The following attestation bundles were made for oefp-0.3.0-cp310-abi3-manylinux_2_34_aarch64.whl:

Publisher: build-wheels.yml on scott-arne/oefp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file oefp-0.3.0-cp310-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for oefp-0.3.0-cp310-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 4bb7081f2abcebdaf649794125da0f3be915ca461910e4fb1cd880e7f1abf17d
MD5 841e1eb6275585cd6e8b5f5a76b7d9ab
BLAKE2b-256 0b9b958ade2c0b57f8b1d2b614827bbb3e745b24da5abaa421d7ee0951b9b85d

See more details on using hashes here.

Provenance

The following attestation bundles were made for oefp-0.3.0-cp310-abi3-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl:

Publisher: build-wheels.yml on scott-arne/oefp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file oefp-0.3.0-cp310-abi3-macosx_26_0_arm64.whl.

File metadata

  • Download URL: oefp-0.3.0-cp310-abi3-macosx_26_0_arm64.whl
  • Upload date:
  • Size: 1.4 MB
  • Tags: CPython 3.10+, macOS 26.0+ ARM64
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for oefp-0.3.0-cp310-abi3-macosx_26_0_arm64.whl
Algorithm Hash digest
SHA256 6c80ab7b4d52425b994e65f29b1801df6b477087ce47d607649fc146e4be311c
MD5 580b298a11aa5dd4961edd72aba3922e
BLAKE2b-256 3eaa1b63cca28a5b550bf3cd8e5836c2d0e71de4becbf0026e97d866f883040c

See more details on using hashes here.

Provenance

The following attestation bundles were made for oefp-0.3.0-cp310-abi3-macosx_26_0_arm64.whl:

Publisher: build-wheels.yml on scott-arne/oefp

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

This release

0.3.0 This release

4 files

0.2.12

4 files

0.2.8

4 files

0.2.7

4 files

0.2.6

4 files

0.2.5

4 files

0.2.4

4 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page