Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Searches molecular libraries for substructure matches using SMARTS patterns with RDKit. Filters compounds by pharmacophore features, functional groups, or scaffold matches with atom mapping. Use when finding compounds containing specific chemical moieties or filtering libraries by structural features.
.claude/skills/bio-substructure-search/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-06 | ✗→✓ | ▲ Improved | — | — |
| case-02 | ✓→✓ | = Same ✓ | — | — |
| case-22 | ✓→✓ | = Same ✓ | — | — |
| case-08 | ✓→✓ | = Same ✓ | — | — |
| case-03 | ✗→✗ | = Same ✗ | — | — |
Reference examples tested with: RDKit 2024.03+
Before using code patterns, verify installed versions match. If versions differ:
pip show <package> then help(module.function) to check signaturesIf code throws ImportError, AttributeError, or TypeError, introspect the installed package and adapt the example to match the actual API rather than retrying.
"Filter my library for compounds containing a specific functional group" → Search molecular collections for substructure matches using SMARTS patterns, identifying compounds that contain specified chemical moieties, scaffolds, or pharmacophore features.
mol.HasSubstructMatch(), Chem.MolFromSmarts() (RDKit)Find molecules containing specific structural patterns using SMARTS.
pythonfrom rdkit import Chem mol = Chem.MolFromSmiles('c1ccc(O)cc1CCO') # Check if pattern exists pattern = Chem.MolFromSmarts('[OH]') # Hydroxyl group has_hydroxyl = mol.HasSubstructMatch(pattern) print(f'Contains hydroxyl: {has_hydroxyl}') # Get all matches (atom indices) matches = mol.GetSubstructMatches(pattern) print(f'Hydroxyl positions: {matches}')
| Pattern | SMARTS | Description | |---------|--------|-------------| | Hydroxyl | [OH] | Alcohol/phenol | | Primary amine | [NH2] | Primary amine | | Secondary amine | [NH1] | Secondary amine | | Carboxylic acid | [CX3](=O)[OX2H1] | COOH | | Amide | [CX3](=O)[NX3] | C(=O)N | | Benzene | c1ccccc1 | Phenyl ring | | Any aromatic | [a] | Any aromatic atom | | Halogen | [F,Cl,Br,I] | Any halogen |
Goal: Filter a molecular library to retain only compounds containing (or lacking) a specific structural pattern.
Approach: Parse a SMARTS pattern and test each molecule for a substructure match, returning those that pass the inclusion or exclusion criterion.
pythonfrom rdkit import Chem def filter_by_substructure(molecules, smarts, exclude=False): ''' Filter molecules by substructure presence/absence. Args: molecules: List of RDKit mol objects smarts: SMARTS pattern string exclude: If True, return molecules WITHOUT the pattern ''' pattern = Chem.MolFromSmarts(smarts) if pattern is None: raise ValueError(f'Invalid SMARTS: {smarts}') filtered = [] for mol in molecules: if mol is None: continue has_match = mol.HasSubstructMatch(pattern) if exclude: if not has_match: filtered.append(mol) else: if has_match: filtered.append(mol) return filtered # Filter for amines amines = filter_by_substructure(library, '[NX3;H2,H1,H0]') # Exclude reactive groups clean = filter_by_substructure(library, '[N+]([O-])=O', exclude=True) # No nitro
Goal: Apply multiple inclusion and exclusion substructure filters to narrow a compound set.
Approach: Sequentially apply SMARTS-based inclusion filters (must match all) then exclusion filters (must match none) to progressively narrow the library.
pythondef filter_multiple_patterns(molecules, include_patterns=None, exclude_patterns=None): ''' Filter by multiple inclusion and exclusion patterns. ''' result = list(molecules) if include_patterns: for smarts in include_patterns: pattern = Chem.MolFromSmarts(smarts) result = [m for m in result if m and m.HasSubstructMatch(pattern)] if exclude_patterns: for smarts in exclude_patterns: pattern = Chem.MolFromSmarts(smarts) result = [m for m in result if m and not m.HasSubstructMatch(pattern)] return result # Find compounds with both amine and carboxylic acid (amino acids) amino_acids = filter_multiple_patterns( library, include_patterns=['[NX3;H2]', '[CX3](=O)[OX2H1]'] )
pythonfrom rdkit import Chem def get_substructure_atoms(mol, smarts): ''' Get all atoms matching a pattern with their indices. ''' pattern = Chem.MolFromSmarts(smarts) matches = mol.GetSubstructMatches(pattern) results = [] for match in matches: atoms = [mol.GetAtomWithIdx(i) for i in match] results.append({ 'indices': match, 'symbols': [a.GetSymbol() for a in atoms] }) return results # Find and characterize all aromatic rings mol = Chem.MolFromSmiles('c1ccc2c(c1)cccc2') rings = get_substructure_atoms(mol, 'c1ccccc1') print(f'Found {len(rings)} aromatic 6-membered rings')
python# Recursive SMARTS for complex patterns # Phenyl attached to carbonyl pattern = '[$(c1ccccc1C(=O))]' # Ortho-substituted phenyl ortho_pattern = '[$(c1ccc([*])cc1[*])]' # Electron-withdrawing group on aromatic ewg_aromatic = '[$(c[$(C(=O)),$(C#N),$(N(=O)=O)])]' mol = Chem.MolFromSmiles('c1ccc(C(=O)O)cc1') pattern = Chem.MolFromSmarts('[$(c1ccccc1C(=O))]') print(mol.HasSubstructMatch(pattern)) # True
pythonfrom rdkit.Chem.Draw import rdMolDraw2D def draw_with_highlights(mol, smarts, filename): '''Draw molecule with substructure highlighted.''' pattern = Chem.MolFromSmarts(smarts) match = mol.GetSubstructMatch(pattern) if not match: print('No match found') return drawer = rdMolDraw2D.MolDraw2DCairo(400, 300) drawer.DrawMolecule(mol, highlightAtoms=match) drawer.FinishDrawing() with open(filename, 'wb') as f: f.write(drawer.GetDrawingText()) # Highlight carboxylic acid draw_with_highlights(mol, '[CX3](=O)[OX2H1]', 'highlighted.png')
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-02 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | pass→pass | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +5 percentage points is the difference between those two pass rates over the 22 comparable cases.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.