Positional Analogue Scanning

In a recent paper in the Journal of Medicinal Chemistry, Lewis Pennington, Ingo Muegge, and coworkers at Alkermes present an overview of Positional Analogue Scanning (PAS), a widely used technique in Medicinal Chemistry.  This is a simple, yet powerful, technique where one "walks" a particular substituent around the structure of a lead molecule and explores a variety of substitutions.   As an example, consider the case below from the paper where each aromatic "cH" is sequentially replaced with nitrogen.

As Pennington and coworkers point out, these small changes can often have profound effects on the properties, metabolism, and biological activity of a molecule.  In addition to being able to generate ideas for new molecules, PAS  can be a powerful tool for computational chemists.  By generating a set of positional analogs, one can rapidly investigate how structural changes impact computed properties.  This information can be used to understand SAR and prioritize molecules for synthesis.  There are several areas where PAS can be combined with computational analysis.

  • Evaluating the impact of small changes in chemical structure on conformation.   By comparing conformational ensembles between two related molecules we can see which changes will, and will not, impact conformation.  
  • Examine how positional analogs impact a molecule's electrostatic potential. 
  • Docking the analogs and examining which changes preserve key interactions and which ones make new interactions. 
  • Carrying out free energy calculations to predict changes that will potentially improve a compound's binding affinity. 
We could, of course, do the positional scanning manually, but that quickly becomes tedious and potentially error-prone.  Instead, let's use the RDKit to write a couple of functions that will enable us to do PAS on any molecule.  Let's start with a function that performs a "nitrogen walk" and generates a set of molecules like the one shown above.  As usual, all of the code to accompany this post is in a Jupyter notebook on GitHub.

def nitrogen_walk(mol_in, num_N=1):
    out_mol_list = []
    used = set()
    aromatic_cH = Chem.MolFromSmarts("[cH]")
    match_atms = [x[0] for x in mol_in.GetSubstructMatches(aromatic_cH)]
    n_combos = combinations(match_atms, num_N)
    for combo in n_combos:
        new_mol = Chem.RWMol(mol_in)
        for idx in combo:
            atm = new_mol.GetAtomWithIdx(idx)
            atm.SetAtomicNum(7)
        smi = Chem.MolToSmiles(new_mol)
        if smi not in used:
            used.add(smi)
            out_mol_list.append(new_mol)
    return out_mol_list

In this function, we begin by finding all of the aromatic carbon atoms in the molecule that have one attached hydrogen.  We then iterate through these, changing carbon atoms to nitrogen.  In addition to changing single atoms, we can use the nun_N argument to the function to indicate the number of carbons that should be changed to nitrogen at each iteration.  This way we can evaluate all pairs, triples, etc.  As we bump the number of nitrogens up, we can also get some pretty silly molecules. 

One other common PAS task is walking a fluorine or a methyl group around the molecule.  We can write a slightly different version of the function above to perform this task. 

def attach_atom(mol_in, atomic_symbol="F", smarts="[cH]", num_sub=1):
    pt = Chem.GetPeriodicTable()
    atomic_num = pt.GetAtomicNumber(atomic_symbol)
    out_mol_list = []
    used = set()
    query = Chem.MolFromSmarts(smarts)
    match_atms = [x[0] for x in mol_in.GetSubstructMatches(query)]
    n_combos = combinations(match_atms, num_sub)
    for combo in n_combos:
        new_mol = Chem.RWMol(mol_in)
        for idx in combo:
            new_idx = new_mol.AddAtom(Chem.Atom(atomic_num))
            new_mol.AddBond(idx, new_idx, order=Chem.rdchem.BondType.SINGLE)
        Chem.SanitizeMol(new_mol)
        smi = Chem.MolToSmiles(new_mol)
        if smi not in used:
            used.add(smi)
            out_mol_list.append(new_mol)
    return out_mol_list

In this case, we again identify the atoms we want to substitute (aromatic carbons by default), and instead of changing the atom type, we add a new atom and a bond to that atom.   This way we can make a simple function call and generate all mono-fluoro analogs of a molecule. 

fluoro_mol_list = attach_atom(mol)



By making a small change in the function call above, we can walk a methyl group around the molecule.   

methyl_mol_list = attach_atom(mol, atomic_symbol="C")

Of course, this function only allows single atom additions, if you want to add larger groups, see my previous post on the CReM software.  Positional scanning isn't rocket science, nor is the code I shared here.  However, both the technique and the code can provide powerful tools for exploring SAR. 






Comments

  1. I have a full implementation here, if people are interested:
    https://github.com/UnixJunkie/molenc/blob/master/bin/molenc_panascan.py

    ReplyDelete
  2. As a single Python function, I got this:
    ---
    from rdkit import Chem
    from rdkit.Chem import AllChem

    def positional_analog_scan(mol, smarts_patt = '[cH]',
    smi_substs = ['N','CF','CC','CO',
    'CCN','CCl','CC(F)(F)(F)','COC']):
    res = []
    ss = set() # a string set
    patt = Chem.MolFromSmarts(smarts_patt)
    for smi in smi_substs:
    subst = Chem.MolFromSmiles(smi)
    analogs = AllChem.ReplaceSubstructs(mol, patt, subst)
    for a in analogs:
    analog_smi = Chem.MolToSmiles(a) # canonicalization
    # remove duplicates
    if analog_smi not in ss:
    res.append(analog_smi)
    ss.add(analog_smi)
    return res
    ---

    ReplyDelete

Post a Comment

Popular posts from this blog

We Need Better Benchmarks for Machine Learning in Drug Discovery

AI in Drug Discovery 2023 - A Highly Opinionated Literature Review (Part I)

Getting Real with Molecular Property Prediction