Cheminformatics basics - A SMARTS way to filter molecules

Walkthrough about what are SMARTS and how they can be used for substructure filtering
Published

January 10, 2022


The code in this notebook is inspired from: * iwatobipen * PatWalters

Pattern matching

In molecules

The SMART way

SMARTS (SMiles ARbitrary Target Specification) is a lanuguage used to search, select, and match a substructure pattern in a molecule SMILE. The idea of SMARTS is reminiscent of regular expressions (regex) for texts. Following some pre-defined rules for searching, SMARTS offer a powerful way to systematically search though a large corpus of molecules for a particular chemical phenotype.

More details on the SMARTS and its ‘grammar’ can be found on the Daylight’s official page

Few words of caution: * SMILES represent whole molecule (graph), SMARTS identify a substructure (subgraph). * All SMILES are valid SMARTS * It is better to be precise with your query than be general (you dont know what it might hit if not meticulous) > For instance, the SMILES O means an aliphatic oxygen with zero charge and two hydrogens, i.e. water. In SMARTS, the same expression means any aliphatic oxygen regardless of charge, hydrogen count, etc, e.g. it will match the oxygen in water, but also those in ethanol, acetone, molecular oxygen, hydroxy and hydronium ions, etc. Specifying [OH2] limits the pattern to match only water (this is also the fully specified SMILES for water).

SMARTS Atomic Primitives
Symbol Symbol name Atomic property requirements Default
wildcard any atom (no default)
a aromatic aromatic (no default)
A aliphatic aliphatic (no default)
D<n> degree <n> explicit connections exactly one
H<n> total-H-count <n> attached hydrogens exactly one1
h<n> implicit-H-count <n> implicit hydrogens at least one
R<n> ring membership in <n> SSSR rings any ring atom
r<n> ring size in smallest SSSR ring of size <n> any ring atom2
v<n> valence total bond order <n> exactly one2
X<n> connectivity <n> total connections exactly one2
x<n> ring connectivity <n> total ring connections at least one2
  • <n>
negative charge -<n> charge -1 charge (– is -2, etc)
+<n> positive charge +<n> formal charge +1 charge (++ is +2, etc)
#n atomic number atomic number <n> (no default)2
@ chirality anticlockwise anticlockwise, default class2
@@ chirality clockwise clockwise, default class2
@<c><n> chirality chiral class <c> chirality <n> (nodefault)
@<c><n>? chiral or unspec chirality <c><n> or unspecified (no default)
<n> atomic mass explicit atomic mass unspecified mass
Examples1:
C aliphatic carbon atom
c aromatic carbon atom
a aromatic atom
[#6] carbon atom
[Ca] calcium atom
[++] atom with a +2 charge
[R] atom in any ring
[D3] atom with 3 explicit bonds (implicit H’s don’t count)
[X3] atom with 3 total bonds (includes implicit H’s)
[v3] atom with bond orders totaling 3 (includes implicit H’s)
CC@HO match chirality (H-F-O anticlockwise viewed from C)
CC@?HO matches if chirality is as specified or is not specified
cc any pair of attached aromatic carbons
c:c aromatic carbons joined by an aromatic bond
c-c aromatic carbons joined by a single bond (e.g. biphenyl).
O any aliphatic oxygen
[O;H1] simple hydroxy oxygen
[O;D1] 1-connected (hydroxy or hydroxide) oxygen
[O;D2] 2-connected (etheric) oxygen
[C,c] any carbon
F,Cl,Br,I] the 1st four halogens.
[N;R] must be aliphatic nitrogen AND in a ring
[!C;R] ( NOTaliphatic carbon ) AND in a ring
[n;H1] H-pyrrole nitrogen
[n&H1] same as above
[c,n&H1] any arom carbon OR H-pyrrole nitrogen
[c,n;H1] (arom carbon OR arom nitrogen) and exactly one H
*!@* two atoms connected by a non-ringbond
@;!: two atoms connected by a non-aromatic ringbond
[C,c]=,#[C,c] two carbons connected by a double or triple bond

Query files

Greg Landrum of Rdkit on query files

Pre-defined filters

In chemical reactions

The Reaction SMARTS way

SMIRKS

<tr>
  <td align="center">C&gt;&gt;C</td>
  <td align="center">CC&gt;&gt;CC</td>
  <td align="center">4</td>
  <td>No maps, normal match.</td>
</tr>     
<tr>
  <td align="center">C&gt;&gt;C</td>
  <td align="center">[CH3:7][CH3:8]&gt;&gt; [CH3:7][CH3:8]</td>
  <td align="center">4</td>
  <td>No maps in query, maps in target are ignored.</td>
</tr>
<tr>
  <td align="center">[C:1]&gt;&gt;C</td>
  <td align="center">[CH3:7][CH3:8]&gt;&gt; [CH3:7][CH3:8]</td>
  <td align="center">4</td>
  <td>Unpaired map in query ignored.</td>
</tr>
<tr>
  <td align="center">[C:1]&gt;&gt;[C:1]</td>
  <td align="center">CC&gt;&gt;CC</td>
  <td align="center">0</td>
  <td>No maps in target, hence no matches.</td>
</tr>
<tr>
  <td align="center">[C:?1]&gt;&gt;[C:?1]</td>
  <td align="center">CC&gt;&gt;CC</td>
  <td align="center">4</td>
  <td>Query says mapped as shown or not present.</td>
</tr>
<tr>
  <td align="center">[C:1]&gt;&gt;[C:1]</td>
  <td align="center">[CH3:7][CH3:8]&gt;&gt;[CH3:7][CH3:8]</td>
  <td align="center">2</td>
  <td>Matches for target 7,7 and 8,8 atom pairs.</td>
</tr>
<tr>
  <td align="center">[C:1]&gt;&gt;[C:2]</td>
  <td align="center">[CH3:7][CH3:8]&gt;&gt; [CH3:7][CH3:8]</td>
  <td align="center">4</td>
  <td>When a query class is not found on both<br>sides of the query, it is
  ignored;<br>this query does NOT say that the atoms<br>are in different
  classes. </td>
</tr>
<tr>
  <td align="center">[C:1][C:1]&gt;&gt;[C:1]</td>
  <td align="center">[CH3:7][CH3:7]&gt;&gt; [CH3:7][CH3:7]</td>
  <td align="center">4</td>
  <td>Atom maps match with "or" logic.  All atoms<br>get bound to
  class 7.</td>
</tr>
<tr>
  <td align="center">[C:1][C:1]&gt;&gt;[C:1]</td>
  <td align="center">[CH3:7][CH3:8]&gt;&gt; [CH3:7][CH3:8]</td>
  <td align="center">4</td>
  <td>The reactant atoms are bound to classes 7<br>and 8. Note that having
  the first query atom<br>bound to class 7 does not preclude<br>binding the
  second atom. Next, the product<br>atom can bind to classes 7 or 8.</td>
</tr>
<tr>
  <td align="center">[C:1][C:1]&gt;&gt;[C:1]</td>
  <td align="center">[CH3:7][CH3:7]&gt;&gt; [CH3:7][CH3:8]</td>
  <td align="center">2</td>
  <td>The reactants are bound to class 7.  The<br>product atom can bind to
  class 7 only.</td>
</tr>
Example Reaction SMARTS:
Query: Target: Matches: Comment:

Other resources:

Relevant literature

  1. Rules for Identifying Potentially Reactive or Promiscuous Compounds | Journal of Medicinal Chemistry (acs.org)

  2. Baell, J. B.; Holloway, G. A. New Substructure Filters for Removal of Pan Assay Interference Compounds (PAINS) from Screening Libraries and for Their Exclusion in Bioassays. J. Med. Chem. 2010.

  3. Vidler, L. R.; Watson, I. A.; Margolis, B. J.; Cummins, D. J.; Brunavs, M. Investigating the Behavior of Published PAINS Alerts Using a Pharmaceutical Company Dataset. ACS Med. Chem. Lett. 2018.

  4. Schuffenhauer, A. et al. Evolution of Novartis’ small molecule screening deck design, J. Med. Chem. (2020)

  5. Gomez-Sanchez, Ruben et al. “Maintaining a High-Quality Screening Collection: The GSK Experience.” SLAS discovery : advancing life sciences R & D vol. 26,8 (2021): 1065-1070. doi:10.1177/24725552211017526

Install necessary modules

# collapse_output
# Install requirements for the tutorial
!pip install pandas rdkit-pypi mols2grid matplotlib scikit-learn ipywidgets
Defaulting to user installation because normal site-packages is not writeable
Requirement already satisfied: pandas in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (1.1.5)
Requirement already satisfied: rdkit-pypi in /home/l017301/.local/lib/python3.8/site-packages (2021.9.4)
Requirement already satisfied: mols2grid in /home/l017301/.local/lib/python3.8/site-packages (0.2.1)
Requirement already satisfied: matplotlib in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (3.3.0)
Requirement already satisfied: scikit-learn in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (0.23.2)
Requirement already satisfied: ipywidgets in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (7.5.1)
Requirement already satisfied: numpy>=1.15.4 in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from pandas) (1.18.5)
Requirement already satisfied: python-dateutil>=2.7.3 in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from pandas) (2.8.1)
Requirement already satisfied: pytz>=2017.2 in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from pandas) (2020.1)
Requirement already satisfied: Pillow in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from rdkit-pypi) (7.2.0)
Requirement already satisfied: jinja2>=2.11.0 in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from mols2grid) (2.11.2)
Requirement already satisfied: pyparsing!=2.0.4,!=2.1.2,!=2.1.6,>=2.0.3 in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from matplotlib) (2.4.7)
Requirement already satisfied: kiwisolver>=1.0.1 in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from matplotlib) (1.2.0)
Requirement already satisfied: cycler>=0.10 in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from matplotlib) (0.10.0)
Requirement already satisfied: joblib>=0.11 in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from scikit-learn) (0.16.0)
Requirement already satisfied: threadpoolctl>=2.0.0 in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from scikit-learn) (2.1.0)
Requirement already satisfied: scipy>=0.19.1 in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from scikit-learn) (1.4.1)
Requirement already satisfied: traitlets>=4.3.1 in /home/l017301/.local/lib/python3.8/site-packages (from ipywidgets) (5.1.1)
Requirement already satisfied: ipython>=4.0.0; python_version >= "3.3" in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from ipywidgets) (7.17.0)
Requirement already satisfied: widgetsnbextension~=3.5.0 in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from ipywidgets) (3.5.1)
Requirement already satisfied: nbformat>=4.2.0 in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from ipywidgets) (5.0.7)
Requirement already satisfied: ipykernel>=4.5.1 in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from ipywidgets) (5.3.4)
Requirement already satisfied: six>=1.5 in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from python-dateutil>=2.7.3->pandas) (1.15.0)
Requirement already satisfied: MarkupSafe>=0.23 in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from jinja2>=2.11.0->mols2grid) (1.1.1)
Requirement already satisfied: decorator in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from ipython>=4.0.0; python_version >= "3.3"->ipywidgets) (4.4.2)
Requirement already satisfied: prompt-toolkit!=3.0.0,!=3.0.1,<3.1.0,>=2.0.0 in /home/l017301/.local/lib/python3.8/site-packages (from ipython>=4.0.0; python_version >= "3.3"->ipywidgets) (3.0.24)
Requirement already satisfied: jedi>=0.10 in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from ipython>=4.0.0; python_version >= "3.3"->ipywidgets) (0.17.1)
Requirement already satisfied: pexpect; sys_platform != "win32" in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from ipython>=4.0.0; python_version >= "3.3"->ipywidgets) (4.8.0)
Requirement already satisfied: pickleshare in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from ipython>=4.0.0; python_version >= "3.3"->ipywidgets) (0.7.5)
Requirement already satisfied: backcall in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from ipython>=4.0.0; python_version >= "3.3"->ipywidgets) (0.2.0)
Requirement already satisfied: pygments in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from ipython>=4.0.0; python_version >= "3.3"->ipywidgets) (2.6.1)
Requirement already satisfied: setuptools>=18.5 in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from ipython>=4.0.0; python_version >= "3.3"->ipywidgets) (47.1.0)
Requirement already satisfied: notebook>=4.4.1 in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from widgetsnbextension~=3.5.0->ipywidgets) (6.1.1)
Requirement already satisfied: jupyter-core in /home/l017301/.local/lib/python3.8/site-packages (from nbformat>=4.2.0->ipywidgets) (4.9.1)
Requirement already satisfied: jsonschema!=2.5.0,>=2.4 in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from nbformat>=4.2.0->ipywidgets) (3.2.0)
Requirement already satisfied: ipython-genutils in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from nbformat>=4.2.0->ipywidgets) (0.2.0)
Requirement already satisfied: tornado>=4.2 in /home/l017301/.local/lib/python3.8/site-packages (from ipykernel>=4.5.1->ipywidgets) (6.1)
Requirement already satisfied: jupyter-client in /home/l017301/.local/lib/python3.8/site-packages (from ipykernel>=4.5.1->ipywidgets) (7.1.2)
Requirement already satisfied: wcwidth in /home/l017301/.local/lib/python3.8/site-packages (from prompt-toolkit!=3.0.0,!=3.0.1,<3.1.0,>=2.0.0->ipython>=4.0.0; python_version >= "3.3"->ipywidgets) (0.2.5)
Requirement already satisfied: parso<0.8.0,>=0.7.0 in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from jedi>=0.10->ipython>=4.0.0; python_version >= "3.3"->ipywidgets) (0.7.0)
Requirement already satisfied: ptyprocess>=0.5 in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from pexpect; sys_platform != "win32"->ipython>=4.0.0; python_version >= "3.3"->ipywidgets) (0.6.0)
Requirement already satisfied: terminado>=0.8.3 in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets) (0.8.3)
Requirement already satisfied: prometheus-client in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets) (0.8.0)
Requirement already satisfied: argon2-cffi in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets) (20.1.0)
Requirement already satisfied: Send2Trash in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets) (1.5.0)
Requirement already satisfied: pyzmq>=17 in /home/l017301/.local/lib/python3.8/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets) (22.3.0)
Requirement already satisfied: nbconvert in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets) (5.6.1)
Requirement already satisfied: pyrsistent>=0.14.0 in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from jsonschema!=2.5.0,>=2.4->nbformat>=4.2.0->ipywidgets) (0.16.0)
Requirement already satisfied: attrs>=17.4.0 in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from jsonschema!=2.5.0,>=2.4->nbformat>=4.2.0->ipywidgets) (19.3.0)
Requirement already satisfied: nest-asyncio>=1.5 in /home/l017301/.local/lib/python3.8/site-packages (from jupyter-client->ipykernel>=4.5.1->ipywidgets) (1.5.4)
Requirement already satisfied: entrypoints in /home/l017301/.local/lib/python3.8/site-packages (from jupyter-client->ipykernel>=4.5.1->ipywidgets) (0.3)
Requirement already satisfied: cffi>=1.0.0 in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from argon2-cffi->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets) (1.14.1)
Requirement already satisfied: defusedxml in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets) (0.6.0)
Requirement already satisfied: mistune<2,>=0.8.1 in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets) (0.8.4)
Requirement already satisfied: testpath in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets) (0.4.4)
Requirement already satisfied: pandocfilters>=1.4.1 in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets) (1.4.2)
Requirement already satisfied: bleach in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets) (3.1.5)
Requirement already satisfied: pycparser in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from cffi>=1.0.0->argon2-cffi->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets) (2.20)
Requirement already satisfied: packaging in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from bleach->nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets) (20.4)
Requirement already satisfied: webencodings in /lrlhps/apps/python/python-3.8.5/lib/python3.8/site-packages (from bleach->nbconvert->notebook>=4.4.1->widgetsnbextension~=3.5.0->ipywidgets) (0.5.1)
WARNING: You are using pip version 20.2.2; however, version 22.2.2 is available.
You should consider upgrading via the '/lrlhps/apps/python/python-3.8.5/bin/python -m pip install --upgrade pip' command.
import os 
import pandas as pd
import numpy as np 

The majority of the basic molecular functionality is found in module rdkit.Chem

# RDkit imports
import rdkit
from rdkit import Chem #This gives us most of RDkits's functionality
from rdkit.Chem import Draw
from rdkit.Chem.Draw import IPythonConsole #Needed to show molecules
IPythonConsole.ipython_useSVG=True  #SVG's tend to look nicer than the png counterparts
print(rdkit.__version__)

# Mute all errors except critical
Chem.WrapLogs()
lg = rdkit.RDLogger.logger() 
lg.setLevel(rdkit.RDLogger.CRITICAL)
2021.09.3
from collections import defaultdict
from rdkit.Chem.Draw import rdMolDraw2D
from IPython.display import SVG
#----- PLOTTING PARAMS ----# 
import matplotlib.pyplot as plt
from matplotlib.pyplot import cm

# High DPI rendering for mac
%config InlineBackend.figure_format = 'retina'

# Plot matplotlib plots with white background: 
%config InlineBackend.print_figure_kwargs={'facecolor' : "w"}

plot_params = {
'font.size' : 15,
'axes.titlesize' : 15,
'axes.labelsize' : 15,
'axes.labelweight' : 'bold',
'xtick.labelsize' : 12,
'ytick.labelsize' : 12,
}
 
plt.rcParams.update(plot_params)
diclofenac = Chem.MolFromSmiles('O=C(O)Cc1ccccc1Nc1c(Cl)cccc1Cl')
diclofenac
# Code from : https://www.rdkit.org/docs/GettingStartedInPython.html?highlight=maccs#drawing-molecules
sub_pattern = Chem.MolFromSmarts('O=CCccN')
hit_ats = list(diclofenac.GetSubstructMatch(sub_pattern))
hit_bonds = []

for bond in sub_pattern.GetBonds():
    aid1 = hit_ats[bond.GetBeginAtomIdx()]
    aid2 = hit_ats[bond.GetEndAtomIdx()]
    
    hit_bonds.append( diclofenac.GetBondBetweenAtoms(aid1, aid2).GetIdx() )
d2d = rdMolDraw2D.MolDraw2DSVG(400, 400) # or MolDraw2DCairo to get PNGs
rdMolDraw2D.PrepareAndDrawMolecule(d2d, diclofenac, highlightAtoms=hit_ats,  highlightBonds=hit_bonds)
d2d.FinishDrawing()
SVG(d2d.GetDrawingText())

Defining a function to make it easy and reproducible:

def viz_substruct(main_smile, substructure_smarts):
    
    mol_file = Chem.MolFromSmiles(main_smile)
    sub_pattern = Chem.MolFromSmarts(substructure_smarts)
    
    hit_ats = list(mol_file.GetSubstructMatch(sub_pattern)) # Returns the indices of the molecule’s atoms that match a substructure query
    hit_bonds = []

    for bond in sub_pattern.GetBonds():
        aid1 = hit_ats[bond.GetBeginAtomIdx()]
        aid2 = hit_ats[bond.GetEndAtomIdx()]

        hit_bonds.append( mol_file.GetBondBetweenAtoms(aid1, aid2).GetIdx() )

    d2d = rdMolDraw2D.MolDraw2DSVG(400, 400) # or MolDraw2DCairo to get PNGs
    rdMolDraw2D.PrepareAndDrawMolecule(d2d, mol_file, highlightAtoms=hit_ats,  highlightBonds=hit_bonds)
    d2d.FinishDrawing()
    return SVG(d2d.GetDrawingText())
viz_substruct('C1=NC(=NC(=C1)C)C2=CC=CC=N2','[ar]!@[ar]')

Online resource to visualize the SMARTS.

This code is inspired from Pen Taka’s blogpost

SMARTS.plus is a nice web GUI to visualize different SMART queries real-time.

from rdkit import Chem
from IPython.display import Image
import requests
import urllib
from time import time 
baseurl = "https://smarts.plus/smartsview/download_rest?"
# Function to get a request for SMART query
def get_img(query):
    url = baseurl+query
    start = time()
    res = requests.get(url)
    _img = Image(res.content, embed=True, retina=True)
    print('Time taken: {0:0.2f} secs'.format(time() - start))
    return _img
im0 = get_img("smarts=[ar]!@[ar]")
im0
Time taken: 4.04 secs

im1 = get_img("smarts=[CX3](=[OX1])[OX2][CX3](=[OX1])")
im1
Time taken: 1.95 secs

SMARTS.plus now handles reaction SMARTS, SMIRKS too!

Paper talking about the update

im0 = get_img("smarts=[CH1:1][OH:2].[OH][C:3]=[O:4]>>[C:1][O:2][C:3]=[O:4]")
im0
Time taken: 2.00 secs