Fingerprints¶
COSMolKit exposes fixed-length bit vectors and sparse bit/count forms.
fp.on_bits returns set-bit indexes; fp.to_numpy() returns a dense
uint8 array. See VALIDATION.md
for reference versions and measured comparison coverage.
Scaffold molecules¶
These value-returning transforms leave the original molecule unchanged:
import cosmolkit as ck
mol = ck.mol_from_smiles("O=C1CCCCC1")
mol.murcko_scaffold().to_smiles() # "C1CCCCC1"
mol.net_scaffold().to_smiles() # "*=C1CCCCC1"
mol.murcko_decompose().to_smiles() # "O=C1CCCCC1"
murcko_scaffold follows RDKit MolHash MurckoScaffold: repeatedly remove
terminal atoms and transfer their bond-order contribution to hydrogen counts.
net_scaffold follows MolHash ExtendedMurcko: retain ring/linker atoms
and replace their directly attached substituents with dummy atoms.
murcko_decompose follows ChemTransforms MurckoDecompose: retain rings,
their shortest-path linkers, and atoms double-bonded to retained atoms.
The MolHash transforms require calculated hydrogen counts, and
murcko_decompose requires initialized ring information for nonempty input.
Default SMILES parsing supplies both. Missing source state raises a typed
operation error; these methods do not silently sanitize unprepared input.
Topological Torsion fingerprints¶
Topological Torsion is a separate fingerprint family from
Molecule.fingerprint_topological(). The latter is RDKit’s path/subgraph
RDKFingerprintMol algorithm; Topological Torsion enumerates ordered atom
paths of a configured length and encodes or hashes their atom invariants. The
two names, parameters, and outputs are intentionally not interchangeable.
The modern generator exposes all four RDKit vector forms. Sparse count output
retains unfolded 64-bit torsion ids, sparse bit output applies the generator’s
bit-domain and count-simulation rules, count output folds counts into
fp_size, and bit output returns an explicit Fingerprint of that size:
import cosmolkit as ck
mol = ck.mol_from_smiles("CCCCO")
settings = ck.TopologicalTorsionGeneratorParams(
include_chirality=False,
torsion_atom_count=4,
count_simulation=True,
count_bounds=[1, 2, 4, 8],
fp_size=2048,
)
generator = ck.TopologicalTorsionFingerprintGenerator(params=settings)
sparse_count = mol.fingerprint_topological_torsion_sparse_count_with_generator(generator)
sparse_bit = mol.fingerprint_topological_torsion_sparse_with_generator(generator)
count = mol.fingerprint_topological_torsion_count_with_generator(generator)
bit = mol.fingerprint_topological_torsion_with_generator(generator)
print(sparse_count.nonzero_elements)
print(sparse_bit.on_bits)
print(count.nonzero_elements)
print(bit.on_bits)
The TopologicalTorsionGeneratorParams passed to the constructor controls
include_chirality, torsion_atom_count, count_simulation,
count_bounds, fp_size, bits_per_feature, and
only_shortest_paths. generator.settings returns a live settings view
whose writable fields update the generator. Scalar calls accept a
TopologicalTorsionCallParams object with from_atoms,
ignore_atoms, and custom_atom_invariants. from_atoms selects paths
by endpoint, while ignore_atoms excludes paths containing any selected
atom. only_shortest_paths keeps only source-defined shortest atom paths.
Custom invariants replace the default atom-code generator for that call.
The default atom-code generator reads the molecule’s explicit-valence cache,
as does RDKit. Molecules returned by the default ck.mol_from_smiles()
path and by with_hydrogens() are ready for fingerprinting because both
operations commit the source-defined explicit-valence state. A molecule parsed
with sanitize=False or produced by another operation that invalidates
valence state must be sanitized first, for example with
mol = mol.sanitize(), unless the call supplies
custom_atom_invariants. Missing cache state is reported as a typed
exception rather than being recomputed implicitly inside the fingerprint call.
Topological Torsion provenance uses the shared FingerprintAdditionalOutput container.
Allocate only the outputs needed before the call. This family populates
atom_to_bits, atom_counts, bit_paths, and atoms_per_bit;
bit_info_map belongs to other generator families and remains empty here,
matching RDKit. Parity tests still request and compare that empty container so
the absence of entries is verified rather than assumed.
output = ck.FingerprintAdditionalOutput()
output.allocate_atom_to_bits()
output.allocate_atom_counts()
output.allocate_bit_paths()
output.allocate_atoms_per_bit()
mol.fingerprint_topological_torsion_with_generator(generator, additional_output=output)
print(output.atom_to_bits)
print(output.atom_counts)
print(output.bit_paths)
print(output.atoms_per_bit)
Bulk generator methods preserve input order and accept num_threads:
molecules = [
ck.mol_from_smiles("CCCC"),
ck.mol_from_smiles("CCCCC"),
]
bits = generator.fingerprints(molecules, num_threads=2)
assert bits[0].on_bits == molecules[0].fingerprint_topological_torsion_with_generator(generator).on_bits
assert bits[1].on_bits == molecules[1].fingerprint_topological_torsion_with_generator(generator).on_bits
Additional vector forms have their own parameter semantics:
fingerprint_topological_torsion_sparse_count_legacy()returns the unfolded sparse-count vector and preserves RDKit’s2**width - 1reported-size compatibility behavior.fingerprint_topological_torsion_count_legacy()returns folded sparse counts.fingerprint_topological_torsion_legacy()preserves thebits_per_entryblock sizing and its distinct four-slot[1, 2, 4, 8]versus non-four-slot threshold rule.
All adapters terminate in the same Rust atom-code, path,
torsion-code/hash, and shared fingerprint-vector core. They do not call RDKit
at runtime. Invalid sizes, unsupported source-undefined shifts, malformed
JSON, bad selections, and incompatible custom invariants fail visibly as
typed Python exceptions instead of selecting another fingerprint algorithm.
Rust exposes the corresponding FingerprintError; Rust MoleculeBatch
conveniences report computation failures as indexed BatchValidationError
records. Batches with retained input errors are rejected before computation;
inspect the errors and explicitly filter valid records before querying.
Source-backed topological and Avalon fingerprints¶
Molecule.fingerprint_topological() and
Molecule.fingerprint_avalon() execute source-backed Rust implementations.
The two APIs are source-backed and return a fresh explicit bit vector:
fingerprint_topological()requires the complete RDKitRDKFingerprintMol/RDKitFP generator behavior, including branched-path enumeration, source random-bit generation, density folding, atom invariants, and the exposed path and atom-selection parameters.fingerprint_avalon()follows the Avalon/reaccs bit-vector path, includingbitFlags,isQuery, hydrogen handling, aromaticity passes, and byte-rounded vector semantics.resetVectis an internal adapter detail and is not exposed on COSMolKit’s value-returning API.
The default bit_flags=0xF07FFF matches RDKit’s Python wrapper.
MOL conversion leaves the original molecule unchanged. The fingerprint path
handles molecules without stored coordinates and does not require callers to
generate 2D coordinates first.
mol = ck.mol_from_smiles("c1ccccc1O")
topological = mol.fingerprint_topological(
min_path=1,
max_path=7,
fp_size=2048,
num_bits_per_feature=2,
)
avalon = mol.fingerprint_avalon(
n_bits=512,
is_query=False,
bit_flags=0xF07FFF,
)
print(topological.on_bits)
print(avalon.on_bits)
Connected fragments¶
mol = ck.mol_from_smiles("CC.O.[Na+]")
fragments = mol.fragments()
print([part.to_smiles() for part in fragments]) # ["CC", "O", "[Na+]"]
print(mol.largest_fragment().to_smiles()) # "CC"
Fragments are sanitized, retain source component order and copy their
conformer rows. Both methods return new molecules without changing mol.
largest_fragment() chooses the most atoms and the last component on ties;
it is not RDKit MolStandardize’s chemically ranked fragment chooser. Empty
input returns an empty fragment list; requesting its largest fragment raises
a typed operation error.
Layered fingerprints¶
Molecule.fingerprint_layered() implements RDKit’s Layered fingerprint
algorithm version 0.7.0. RDKit labels this algorithm experimental;
COSMolKit preserves that upstream metadata even though the exposed parameter
and result types are ordinary project-native APIs. The six active source layer
bits are:
Flag |
Layer |
Encoded state |
|---|---|---|
|
topology |
path-local atom degree and bond-neighbor topology |
|
bond order |
normalized bond type plus topology |
|
atom type |
atomic numbers plus topology |
|
ring presence |
sparse ring-bond membership |
|
ring size |
minimum source SSSR ring size |
|
aromaticity |
query-aware endpoint aromaticity plus topology |
0x07 is the source substructure prefix and 0x3f selects all active
layers. The default 0xffffffff retains all ten source flag slots; high bits
have no encoder in RDKit and therefore add no components. They are accepted,
not repurposed as COSMolKit extensions.
Paths are bond paths with inclusive min_path and max_path bounds. The
defaults are 1 through 7, 2,048 output bits, and branched enumeration.
branched_paths=False selects linear bond paths. from_atoms=None means
no root restriction. An explicit empty list is different: it is a present
empty root selection and returns an empty fingerprint. Duplicate and reordered
roots retain the source aggregation order.
import cosmolkit as ck
molecule = ck.mol_from_smiles("c1ccccc1O")
substructure = molecule.fingerprint_layered(
layers=0x07,
min_path=1,
max_path=7,
fp_size=2048,
)
even_mask = ck.Fingerprint.from_on_bits(257, range(0, 257, 2))
counted = molecule.fingerprint_layered_with_output(
layers=0x3F,
min_path=2,
max_path=4,
fp_size=257,
atom_counts=[10] * molecule.num_atoms,
set_only_bits=even_mask,
branched_paths=True,
from_atoms=[0],
)
print(substructure.on_bits)
print(counted.fingerprint.on_bits)
print(counted.atom_counts)
set_only_bits must have exactly fp_size bits. It gates projected bits
before insertion and before counting. A supplied atom_counts vector is a
seed, not a zeroed output buffer; its length must be at least the molecule atom
count. Every atom in a path is incremented once when any layer projection for
that path passes the mask, even when several layers set bits or bit collisions
occur. Omitting counts makes atom_counts() return None.
Invalid zero/reversed path bounds, zero width, short count vectors, width-mismatched masks, out-of-range roots, and ring-preparation failures are reported as typed exceptions. The operation is read-only: repeated, interleaved, batch, and concurrent calls share the same Rust scalar core and do not mutate molecule topology, properties, coordinates, or derived caches.
Pinned RDKit contains one upstream defect in the unrooted linear call: it requests atom-index paths and later consumes them as bond indices, which can terminate the process for ordinary acyclic molecules. COSMolKit does not copy that crash. It applies the source header’s documented bond-path semantics, the same semantics used by the valid rooted linear branch. This is an explicit process-safety compatibility difference, not a chemistry fallback.
Pattern fingerprints¶
Molecule.fingerprint_pattern() reproduces RDKit’s ordinary-molecule
Pattern fingerprint using the fixed, source-ordered table of 13 SMARTS
queries. The default result has 2,048 bits. tautomeric=True enables the
source’s tautomer-aware structural hash for single, double, and aromatic bond
states:
mol = ck.mol_from_smiles("c1ccccc1O")
pattern = mol.fingerprint_pattern()
tautomeric_pattern = mol.fingerprint_pattern(
n_bits=2048,
tautomeric=True,
)
print(pattern.on_bits)
print(tautomeric_pattern.on_bits)
The call is value-style and does not mutate the molecule. Query-bearing molecules are supported and follow the source’s Pattern-specific atom and bond suppression rules. The compiled SMARTS table is shared across repeated, concurrent, scalar, and batch calls; Pattern calls can be interleaved with the other fingerprint families without shared option state.
n_bits must be greater than zero. SMARTS-compilation and substructure
matching failures remain typed exceptions rather than selecting another
fingerprint algorithm. RDKit labels Pattern fingerprint version 1.0.0
experimental; COSMolKit preserves that upstream metadata.
The pinned RDKit ordinary overload accepts atomCounts and setOnlyBits
but only validates their sizes; their values are otherwise unused and
unchanged. COSMolKit intentionally omits those inert arguments instead of
presenting them as functional options. RDKit’s separate MolBundle overload
intersects member fingerprints. COSMolKit does not expose it because an
ordered MoleculeBatch returns one result per record and is not a bundle.
AtomPair fingerprints¶
Molecule exposes the four source generator result forms through one
project-native implementation:
fingerprint_atom_pair_sparse_count()returns the source-width count map;fingerprint_atom_pair_count()returns the folded count map;fingerprint_atom_pair_sparse()returns the source-width bit set;fingerprint_atom_pair()returns the fixed-width explicit bit vector.
The shared parameter surface covers 2D topological or 3D conformer distances,
minimum and maximum distance, chirality, count simulation and bounds,
num_bits_per_feature, from_atoms, ignore_atoms, conformer selection,
and custom atom invariants. A 3D call requires a conformer and sets
use_2d=False explicitly.
mol = ck.mol_from_smiles("CCCO")
explicit = mol.fingerprint_atom_pair()
sparse_count = mol.fingerprint_atom_pair_sparse_count()
folded_count = mol.fingerprint_atom_pair_count()
sparse_bits = mol.fingerprint_atom_pair_sparse()
print(explicit.on_bits)
print(sparse_count.nonzero_elements)
print(folded_count.nonzero_elements)
print(sparse_bits.on_bits)
Pass a FingerprintAdditionalOutput collector to
fingerprint_atom_pair_with_params() for exact atom counts, atom-to-bit
rows, endpoint-pair bit information, and atoms-per-bit provenance:
output = ck.FingerprintAdditionalOutput()
output.allocate_atom_counts()
output.allocate_atom_to_bits()
output.allocate_bit_info_map()
output.allocate_atoms_per_bit()
fp = mol.fingerprint_atom_pair_with_params(ck.AtomPairFingerprintParams(), output)
print(fp.on_bits)
print(output.atom_counts)
print(output.atom_to_bits)
print(output.bit_info_map)
print(output.atoms_per_bit)
Topological provenance¶
fingerprint_topological_with_output() returns a
TopologicalFingerprintResult. Request atom_bits and/or bit_info to
receive the matching source provenance outputs:
result = mol.fingerprint_topological_with_output(
fp_size=2048,
atom_bits=True,
bit_info=True,
)
print(result.fingerprint.on_bits)
print(result.atom_bits)
print(result.bit_info)
Single Molecules¶
import cosmolkit as ck
mol = ck.mol_from_smiles("c1ccccc1O")
fp = mol.fingerprint_morgan(radius=2, fp_size=2048)
print(fp.n_bits)
print(fp.on_bits)
Tanimoto similarity is computed directly on Fingerprint values:
phenol = ck.mol_from_smiles("c1ccccc1O").fingerprint_morgan()
benzene = ck.mol_from_smiles("c1ccccc1").fingerprint_morgan()
print(phenol.tanimoto(benzene))
Additional Output¶
Pass a FingerprintAdditionalOutput collector to fingerprint_morgan()
to obtain provenance alongside the returned fingerprint:
output = ck.FingerprintAdditionalOutput()
output.allocate_atom_counts()
output.allocate_atom_to_bits()
output.allocate_bit_info_map()
output.allocate_atoms_per_bit()
fp = mol.fingerprint_morgan(
radius=2, fp_size=2048, additional_output=output,
)
print(fp.on_bits)
print(output.atom_counts)
print(output.atom_to_bits)
print(output.bit_info_map)
print(output.atoms_per_bit)
Supported Parameters¶
The short Morgan methods accept a parameter record or configuration keywords:
fp = mol.fingerprint_morgan(radius=2, fp_size=256, include_chirality=True)
params = ck.MorganFingerprintParams(from_atoms=[0])
params.radius = 2
params.fp_size = 256
params.from_atoms = [0, 1]
fp = mol.fingerprint_morgan(params)
Assign sequence fields as a whole; their getters return immutable tuples.
Nested parameter objects, where present, write through to their owning
configuration. Rejected assignments leave the configuration unchanged.
Constructor inputs are copied. Do not combine a parameter record with
configuration keywords, including explicit None. Execution reads the
supplied configuration without changing it.
Morgan configuration includes:
radiusandfp_sizeinclude_chiralityanduse_bond_typescount_simulationandcount_boundsonly_nonzero_invariantsinclude_redundant_environmentsfrom_atomsandignore_atomscustom_atom_invariantsandcustom_bond_invariantsinvariants(MorganInvariants.Connectivity,MorganInvariants.Featuresor explicitMorganInvariants.FeaturePatterns)include_ring_membershipbits_per_feature
Invalid configurations raise typed errors; no other fingerprint algorithm is substituted. See the API reference for exact configuration types.
Batch Fingerprints¶
MoleculeBatch exposes matching batch APIs. If it contains failed records retained
by errors="keep", a fingerprint query raises BatchValidationError before
computation. Inspect batch.errors and filter with batch[batch.valid_mask]
or batch.with_valid_records(). Filtering preserves survivor order but
renumbers rows; keep the original indices separately if needed.
A new calculation failure after filtering still raises an indexed error rather
than returning partial results. Manually constructed FingerprintBatch values
can contain None; their to_numpy() rejects missing rows and unequal widths.
This container rule is distinct from molecule-query preflight.
Computed empty batches retain the requested fingerprint width: for example,
empty.fingerprint_morgan(fp_size=128).to_numpy().shape is (0, 128).
An empty manually constructed
FingerprintBatch([]) has no width information and retains width zero.
import cosmolkit as ck
batch = ck.mols_from_smiles_list(
["CCO", "not-smiles", "CCCO"],
errors="keep",
).with_parallel_jobs(8)
print(batch.errors)
batch = batch.with_valid_records()
fps = batch.fingerprint_morgan(fp_size=2048)
atom_pairs = batch.fingerprint_atom_pair(
fp_size=2048,
n_jobs=8,
progress_bar=False,
)
layered = batch.fingerprint_layered(
layers=0x3F,
min_path=1,
max_path=7,
fp_size=2048,
n_jobs=8,
progress_bar=False,
)
print([fp.on_bits if fp is not None else None for fp in fps])
print([fp.on_bits if fp is not None else None for fp in atom_pairs])
print([fp.on_bits if fp is not None else None for fp in layered])