Creating Atom Mappings#

Once your data has been loaded we can now proceed to defining how Components in these Systems correspond. Mapping objects are used to defined how Component objects from different ChemicalSystem objects are related. This guide will show how this concept applies to the case of a pair of ligands we wish to transform between.

Generating Mappings#

Mappings between small molecules are generated by a LigandAtomMapper, which are reusable classes. These take pairs of openfe.SmallMoleculeComponent objects and suggests zero (in the case that no mapping can be found) or more mappings.

Built in to the openfe package are bindings to the Lomap package, including the openfe.setup.LomapAtomMapper which uses an MCS approach based on the RDKit. This takes various parameters which control how it produces mappings, these are viewable through help(LomapAtomMapper).

This is how we can create a mapping between two ligands:

import openfe
from openfe import setup

# as previously detailed, load a pair of ligands
m1 = SmallMoleculeComponent(...)
m2 = SmallMoleculeComponent(...)

# first create the object which is a mapping producer
mapper = setup.LomapAtomMapper(threed=True)
# this gives an iterable of possible mappings, zero or more!
mapping_gen = mapper.suggest_mappings(m1, m2)
# all possible mappings can be extracted into a list
mappings = list(mapping_gen)
# Lomap always produces a single Mapping, so extract it from the list
mapping = mappings[0]

The first and second ligand molecules put into the suggest_mappings method are then henceforth referred to as componentA and componentB. The correspondence of atoms in these two components is then given via the .componentA_to_componentB attribute, which returns a dictionary of integers. Keys in this dictionary refer to the indices of atoms in the “A” molecule, while the corresponding values refer to indices of atoms in the “B” molecule. If a given index does not appear, then it is unmapped.

Note

Like the Component objects, a Mapping object is immutable once created!

Visualising Mappings#

In an interactive notebook we can view a 2D representation of the mapping. In this view, atoms that are deleted are coloured red, while atoms that undergo an elemental transformation are coloured blue. Similarly, bonds that are deleted are coloured red, while bonds that change, either bond order or are between different elements, are coloured blue.

Sample output of 2d mapping visualisation

These 2D mappings can be saved to file using the LigandAtomMapping.draw_to_file() function.

With the py3dmol package installed, we can also view the mapping in 3D allowing us to manually inspect the spatial overlap of the mapping. In a notebook, this produces an interactive rotatable view of the mapping. The left and rightmost views show the “A” and “B” molecules with coloured spheres on each showing the correspondence between atoms. The centre view shows both molecules overlaid, allowing the spatial correspondence to be directly viewed.

from openfe.utils import visualization_3D

view = visualization_3D.view_mapping_3d(mapping)
Sample output of view_mapping_3d function

The cartesian distance between pairs of atom mapping is also available via the get_distances() method. This returns a numpy array.

mapping.get_distances()

Scoring Mappings#

With many possible mappings, and many ligand pairs we could form mappings between, we use scorers to rate if a mapping is a good idea. These take a LigandAtomMapping object and return a value from 0.0 (indicating a terrible mapping) to 1.0 (indicating a great mapping), and so can be used as objective functions for optimizing ligand networks.

Again, the scoring functions from Lomap are included in the openfe package. The default_lomap_score() function combines many different criteria together such as the number of heavy atoms, if certain chemical changes are present, and if ring sizes are being mutated, into a single value.

from openfe.setup.lomap_scorers

mapping = next(mapper.suggest_mappings(m1, m2))

score = lomap_scorers.default_lomap_scorer(mapping)

As each scoring function returns a normalised value, it is possible to chain together various scoring functions, which is how this default_lomap_score function is constructed!