Skill Tracer
Skills › Scientific Agent Skills › molfeat › examples.md

examples.md

142 lines · skills/molfeat/references/examples.md at 5f2bc03

Open on GitHub
1# Worked Molfeat 1.0 examples
2
3These small CPU examples use Molfeat 1.0.0 with datamol 0.13.0. They are covered by
4local smoke tests. Dataset-scale workflows are illustrative and need project data.
5

Worked Molfeat 1.0 examples

These small CPU examples use Molfeat 1.0.0 with datamol 0.13.0. They are covered by local smoke tests. Dataset-scale workflows are illustrative and need project data.

Raw lines 1 to 5
6## Preprocess explicitly and retain IDs
7
8`preprocess()` is not an automatically invoked transformation hook. Apply a chosen
9standardization policy explicitly; the example below normalizes molecules without
10asserting that all salts, tautomers, or protonation states should be collapsed.
12```python
13import datamol as dm
14import numpy as np
15from molfeat.calc import FPCalculator
16from molfeat.trans import MoleculeTransformer
18rows = [("a", "CCO"), ("bad", "invalid"), ("b", "CC(=O)O")]
19accepted, rejected = [], []
20for record_id, smiles in rows:
21 mol = dm.to_mol(smiles)
22 if mol is None or mol.GetNumAtoms() == 0:
23 rejected.append(record_id)
24 continue
25 mol = dm.standardize_mol(mol)
26 accepted.append((record_id, mol))
28transformer = MoleculeTransformer(
29 FPCalculator("ecfp", radius=2, fpSize=2048), dtype=np.float32,
31X = transformer([mol for _, mol in accepted]) # strict: catch unexpected failures
32assert X.shape == (2, 2048)
33assert [record_id for record_id, _ in accepted] == ["a", "b"]
34assert rejected == ["bad"]
35```
37For real data retain original SMILES and the standardized form with each ID. Confirm
38whether mixtures and protonation variants are distinct assay records before removing
39fragments. Enforce the same policy in validation and deployment.

Preprocess explicitly and retain IDs

preprocess() is not an automatically invoked transformation hook. Apply a chosen standardization policy explicitly; the example below normalizes molecules without asserting that all salts, tautomers, or protonation states should be collapsed.

import datamol as dm
import numpy as np
from molfeat.calc import FPCalculator
from molfeat.trans import MoleculeTransformer

rows = [("a", "CCO"), ("bad", "invalid"), ("b", "CC(=O)O")]
accepted, rejected = [], []
for record_id, smiles in rows:
    mol = dm.to_mol(smiles)
    if mol is None or mol.GetNumAtoms() == 0:
        rejected.append(record_id)
        continue
    mol = dm.standardize_mol(mol)
    accepted.append((record_id, mol))

transformer = MoleculeTransformer(
    FPCalculator("ecfp", radius=2, fpSize=2048), dtype=np.float32,
)
X = transformer([mol for _, mol in accepted])  # strict: catch unexpected failures
assert X.shape == (2, 2048)
assert [record_id for record_id, _ in accepted] == ["a", "b"]
assert rejected == ["bad"]

For real data retain original SMILES and the standardized form with each ID. Confirm whether mixtures and protonation variants are distinct assay records before removing fragments. Enforce the same policy in validation and deployment.

Raw lines 6 to 40
41## Concatenate and persist
43```python
44import numpy as np
45from molfeat.trans import FeatConcat, MoleculeTransformer
46from molfeat.trans.fp import FPVecTransformer
48parts = [FPVecTransformer("maccs"), FPVecTransformer("ecfp:4", length=2048)]
49combined = FeatConcat(parts, dtype=np.float32)
50smiles = ["CCO", "c1ccccc1"]
51X = combined(smiles, enforce_dtype=True)
52assert X.shape == (2, 2215)
54# Save each component state; FeatConcat is a container, not a calculator.
55for i, part in enumerate(parts):
56 part.to_state_json_file(f"component_{i}.json")
57restored = FeatConcat([
58 MoleculeTransformer.from_state_json_file(f"component_{i}.json")
59 for i in range(len(parts))
60])
61np.testing.assert_array_equal(restored(smiles), X)
62```
64Use strict calls after common validation. See the API reference for the 1.0
65`FeatConcat(ignore_errors=True)` row-alignment limitation.

Concatenate and persist

import numpy as np
from molfeat.trans import FeatConcat, MoleculeTransformer
from molfeat.trans.fp import FPVecTransformer

parts = [FPVecTransformer("maccs"), FPVecTransformer("ecfp:4", length=2048)]
combined = FeatConcat(parts, dtype=np.float32)
smiles = ["CCO", "c1ccccc1"]
X = combined(smiles, enforce_dtype=True)
assert X.shape == (2, 2215)

# Save each component state; FeatConcat is a container, not a calculator.
for i, part in enumerate(parts):
    part.to_state_json_file(f"component_{i}.json")
restored = FeatConcat([
    MoleculeTransformer.from_state_json_file(f"component_{i}.json")
    for i in range(len(parts))
])
np.testing.assert_array_equal(restored(smiles), X)

Use strict calls after common validation. See the API reference for the 1.0 FeatConcat(ignore_errors=True) row-alignment limitation.

Raw lines 41 to 66
67## Named and pharmacophore descriptors
69```python
70import numpy as np
71from molfeat.calc import CATS, RDKitDescriptors2D, Pharmacophore2D
73rdkit_2d = RDKitDescriptors2D()
74values = rdkit_2d("CCO")
75assert len(values) == len(rdkit_2d.columns)
76nonfinite_names = np.asarray(rdkit_2d.columns)[~np.isfinite(values)]
78cats = CATS(use_3d_distances=False, scale="raw")
79assert cats("CCO").shape == (189,)
80gobbi = Pharmacophore2D(factory="gobbi", length=2048)
81assert gobbi("c1ccccc1O").shape == (2048,)
82```
84Do not replace all undefined descriptors with zero without documenting why that is
85scientifically meaningful. Fit imputation/column selection on the training fold and
86record the final ordered column list. Optional `MordredDescriptors` defaults to 2D;
87install `molfeat[mordred]` to use it, and inspect missing values separately.

Named and pharmacophore descriptors

import numpy as np
from molfeat.calc import CATS, RDKitDescriptors2D, Pharmacophore2D

rdkit_2d = RDKitDescriptors2D()
values = rdkit_2d("CCO")
assert len(values) == len(rdkit_2d.columns)
nonfinite_names = np.asarray(rdkit_2d.columns)[~np.isfinite(values)]

cats = CATS(use_3d_distances=False, scale="raw")
assert cats("CCO").shape == (189,)
gobbi = Pharmacophore2D(factory="gobbi", length=2048)
assert gobbi("c1ccccc1O").shape == (2048,)

Do not replace all undefined descriptors with zero without documenting why that is scientifically meaningful. Fit imputation/column selection on the training fold and record the final ordered column list. Optional MordredDescriptors defaults to 2D; install molfeat[mordred] to use it, and inspect missing values separately.

Raw lines 67 to 88
89## Generate and check one 3D conformer
91```python
92import datamol as dm
93import numpy as np
94from molfeat.calc import RDKitDescriptors3D, USRDescriptors, CATS
96mol = dm.conformers.generate(
97 dm.to_mol("CCCO"), n_confs=1, random_seed=42,
98 minimize_energy=True, num_threads=1,
100assert mol is not None and mol.GetNumConformers() == 1
101shape = USRDescriptors("USR")(mol)
102assert shape.shape == (12,) and np.isfinite(shape).all()
103rdkit_3d = RDKitDescriptors3D()(mol)
104cats_3d = CATS(use_3d_distances=True)(mol)
105assert cats_3d.shape == (126,)
106```
108The current datamol API is `dm.conformers.generate`, not
109`dm.conform.generate_conformers`. One conformer is a smoke test, not a representative
110conformational ensemble. For a scientific study record sampling, protonation,
111force field, minimization/convergence, coordinates, conformer IDs, and the rule for
112aggregating conformer descriptors. Plain SMILES contain no 3D coordinates.

Generate and check one 3D conformer

import datamol as dm
import numpy as np
from molfeat.calc import RDKitDescriptors3D, USRDescriptors, CATS

mol = dm.conformers.generate(
    dm.to_mol("CCCO"), n_confs=1, random_seed=42,
    minimize_energy=True, num_threads=1,
)
assert mol is not None and mol.GetNumConformers() == 1
shape = USRDescriptors("USR")(mol)
assert shape.shape == (12,) and np.isfinite(shape).all()
rdkit_3d = RDKitDescriptors3D()(mol)
cats_3d = CATS(use_3d_distances=True)(mol)
assert cats_3d.shape == (126,)

The current datamol API is dm.conformers.generate, not dm.conform.generate_conformers. One conformer is a smoke test, not a representative conformational ensemble. For a scientific study record sampling, protonation, force field, minimization/convergence, coordinates, conformer IDs, and the rule for aggregating conformer descriptors. Plain SMILES contain no 3D coordinates.

Raw lines 89 to 113
114## Binary Tanimoto similarity
116```python
117import numpy as np
118from rdkit import DataStructs
119from molfeat.calc import FPCalculator
121calc = FPCalculator("ecfp", radius=2, fpSize=2048, includeChirality=True)
122query = calc("CCO", raw=True)
123library = ["CCO", "CCCO", "c1ccccc1"]
124fingerprints = [calc(s, raw=True) for s in library]
125scores = np.array(DataStructs.BulkTanimotoSimilarity(query, fingerprints))
126assert scores[0] == 1.0
127ranked_positions = np.argsort(-scores, kind="stable")
128```
130Use the same fingerprint configuration for query and reference compounds. Binary
131Tanimoto, count-vector similarity, cosine on embeddings, and distances on scaled
132continuous descriptors encode different assumptions; do not interchange their
133thresholds. Shape descriptors need their own appropriate metric.

Binary Tanimoto similarity

import numpy as np
from rdkit import DataStructs
from molfeat.calc import FPCalculator

calc = FPCalculator("ecfp", radius=2, fpSize=2048, includeChirality=True)
query = calc("CCO", raw=True)
library = ["CCO", "CCCO", "c1ccccc1"]
fingerprints = [calc(s, raw=True) for s in library]
scores = np.array(DataStructs.BulkTanimotoSimilarity(query, fingerprints))
assert scores[0] == 1.0
ranked_positions = np.argsort(-scores, kind="stable")

Use the same fingerprint configuration for query and reference compounds. Binary Tanimoto, count-vector similarity, cosine on embeddings, and distances on scaled continuous descriptors encode different assumptions; do not interchange their thresholds. Shape descriptors need their own appropriate metric.

Raw lines 114 to 134
135## Cache identity and large batches
137A single `embeddings_cache.npz` filename is insufficient provenance. Save numeric
138features with input IDs and a manifest containing standardized input identity,
139configuration/state, model revision/tokenizer/pooling/max length, package versions,
140and row order. Validate the manifest before reuse. Load numeric arrays with
141`allow_pickle=False`. For large datasets, write each chunk or score it immediately;
142retaining all chunks before stacking does not bound total memory.

Cache identity and large batches

A single embeddings_cache.npz filename is insufficient provenance. Save numeric features with input IDs and a manifest containing standardized input identity, configuration/state, model revision/tokenizer/pooling/max length, package versions, and row order. Validate the manifest before reuse. Load numeric arrays with allow_pickle=False. For large datasets, write each chunk or score it immediately; retaining all chunks before stacking does not bound total memory.

Raw lines 135 to 142