Skip to content

Commit

Permalink
MCMC algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
banan314 committed Jun 15, 2019
1 parent d983c7f commit 615ee9b
Show file tree
Hide file tree
Showing 4 changed files with 312 additions and 12 deletions.
142 changes: 142 additions & 0 deletions oddt/docking/MCMCAlgorithm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
from enum import Enum
import numpy as np
from scipy.optimize import minimize

from oddt import random_seed
from oddt.docking.MCMCUtils import MCMCUtils


class OptimizationMethod(Enum):
"""
method of optimization (one of SIMPLEX/NO_OPTIM, NELDER_MEAD or LBFGSB)
"""
SIMPLEX = 1
NO_OPTIM = 1
NELDER_MEAD = 2
LBFGSB = 3


class MCMCAlgorithm(object):
def __init__(self, engine, optim=OptimizationMethod.NELDER_MEAD, optim_iter=10, mc_steps=50,
mut_steps=100, seed=None, ):
"""
Parameters
----------
engine: CustomEngine
engine with prepared molecules and defined scoring function
optim: OptimizationMethod
method of optimization (or SIMPLEX/NO_OPTIM if none) around locally chosen conformation point,
must be one of the values in OptimizationMethod enumeration
optim_iter: int (default=10)
number of iterations for local optimization, in the scipy.optimize.minimize
mc_steps: int (default=50)
number of steps performed by the MCMC algorithm
mut_steps: int (100)
number of mutation steps (while choosing next random conformation)
seed: int
seed for the pseudonumber generators
"""

self.engine = engine
self.ligand = engine.lig
self.optim = optim
self.optim_iter = optim_iter
self.mc_steps = mc_steps
self.mut_steps = mut_steps
if seed:
random_seed(seed)

self.num_rotors = len(self.engine.rotors)

self.lig_dict = self.engine.lig_dict
self.mcmc_utils = MCMCUtils()

def perform(self):
"""
performs the algorithm
Returns
-------
conformation, score: float[], float
best conformation and best score for this conformation
"""

x1 = self.generate_rotor_vector()
c1 = self.engine.lig.mutate(x1)
e1 = self.engine.score(c1)
out = {'score': e1, 'conformation': c1.copy().tolist()}

for _ in range(self.mc_steps):
c2, x2 = self.generate_conformation(x1)
e2 = self.engine.score(c2)
e3, x3 = self._optimize(e2, x2)

delta = e3 - e1

if delta < 0 or np.exp(-delta) > np.random.uniform(): # Metropolis criterion
x1 = x3
if delta < 0:
e1 = e3
conformation = self.engine.lig.mutate(x1.copy())
out = {'score': e1, 'conformation': conformation.tolist()}

return out['conformation'], out['score']

def _optimize(self, e2, x2):

bounds = ((-1., 1.), (-1., 1.), (-1., 1.), (-np.pi, np.pi), (-np.pi, np.pi), (-np.pi, np.pi))
for i in range(len(self.engine.rotors)):
bounds += ((-np.pi, np.pi),)
bounds = np.array(bounds)

if self.optim == OptimizationMethod.SIMPLEX:
return e2, x2
elif self.optim == OptimizationMethod.NELDER_MEAD:
return self._minimize_nelder_mead(x2)
elif self.optim == OptimizationMethod.LBFGSB:
return self._minimize_lbfgsb(bounds, x2)
return e2, x2

def _minimize_nelder_mead(self, x2):

m = minimize(self.mcmc_utils.score_coords, x2, args=(self.engine, self.engine.score),
method='Nelder-Mead')
e3, x3 = self._extract_from_scipy_minimize(m)
return e3, x3

def _extract_from_scipy_minimize(self, m):

x3 = m.x
x3 = self.mcmc_utils.keep_bound(x3)
e3 = m.fun
return e3, x3

def _minimize_lbfgsb(self, bounds, x2):

m = minimize(self.mcmc_utils.score_coords, x2, method='L-BFGS-B',
jac=self.mcmc_utils.score_coords_jac,
args=(self.engine, self.engine.score), bounds=bounds, options={'maxiter': self.optim_iter})
e3, x3 = self._extract_from_scipy_minimize(m)
return e3, x3

def generate_conformation(self, x1):

for _ in range(self.mut_steps):
x2 = self.mcmc_utils.rand_mutate_big(x1.copy())
c2 = self.engine.lig.mutate(x2)
return c2, x2

# generate random coordinate
def generate_rotor_vector(self):

trans_vec = np.random.uniform(-1, 1, size=3)
rot_vec = np.random.uniform(-np.pi, np.pi, size=3)
rotors_vec = np.random.uniform(-np.pi, np.pi, size=self.num_rotors)
return np.hstack((trans_vec, rot_vec, rotors_vec))
123 changes: 123 additions & 0 deletions oddt/docking/MCMCUtils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import numpy as np


class MCMCUtils(object):
def __init__(self):
"""
class containing MCMC utils for MCMC algorithm
"""

def score_coords(self, x, engine, scoring_func):
"""
score using given coordinates
Parameters
----------
x: float[]
conformation
engine: CustomEngine
engine with prepared molecules and defined scoring function
Returns
-------
score: float
score
"""
c1 = engine.lig.mutate(x)
return scoring_func(c1)

def score_coords_jac(self, x, engine, scoring_func, step=1e-2):
"""
jac function for scipy.optimize.minimize
Parameters
----------
x: float[]
conformation
engine: CustomEngine
engine with prepared molecules and defined scoring function
step:
infinitesimal change in x (for computing the gradient)
Returns
-------
grad: float[]
gradient of the scoring function
"""
c1 = engine.lig.mutate(x)
e1 = scoring_func(c1)
grad = []
for i in range(len(x)):
x_g = x.copy()
x_g[i] += step # if i < 3 else 1e-2
cg = engine.lig.mutate(x_g)
grad.append(scoring_func(cg))
return (np.array(grad) - e1) / step

def _random_angle(self, size=1):
"""
generation of random angles, in range from -pi to +pi
Parameters
----------
size: int
number of random angles to generate
Returns
-------
random: float or float[size]
random angle (float) if size=1 or list of random angles (float[]) if size>1
"""
if size > 1:
return np.random.uniform(-np.pi, np.pi, size=size)
return np.random.uniform(-np.pi, np.pi)

def keep_bound(self, x):
"""
keep bound of conformation
Parameters
----------
x: float[]
conformation
Returns
-------
x: float[]
bounded conformation
"""
x[:3] = np.clip(x[:3], -1, 1)
x[3:] = np.clip(x[3:], -np.pi, np.pi)
return x

def rand_mutate_big(self, x):
"""
change elements of conformation randomly
Parameters
----------
x: float[]
conformation
Returns
-------
x: float[]
conformation with randomly changed elements
"""
x = x.copy()
m = np.random.randint(0, len(x) - 4)
if m == 0: # do random translation
x[:3] = np.random.uniform(-0.3, 0.3, size=3)
elif m == 1: # do random rotation step
x[3:6] = self._random_angle(size=3)
else: # do random dihedral change
ix = 6 + m - 2
x[ix] = self._random_angle()
return x
3 changes: 2 additions & 1 deletion oddt/docking/dock.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
from joblib import Parallel, delayed
from oddt.docking.AutodockVina import autodock_vina
from oddt.docking.GeneticAlgorithm import GeneticAlgorithm
from oddt.docking.MCMCAlgorithm import MCMCAlgorithm
from oddt.docking.CustomEngine import CustomEngine
from oddt.docking.internal import write_ligand_to_pdbqt

Expand Down Expand Up @@ -69,7 +70,7 @@ def __init__(self, receptor, ligands, docking_type, scoring_func='interaction_en
for ligand in self.ligands:
custom_engine = CustomEngine(receptor, lig=ligand, scoring_func=scoring_func)
if self.docking_type == 'MCMC':
# custom_engines.append(MCMCAlgorithm(self.custom_engine, **additional_params))
self.custom_engines.append(MCMCAlgorithm(custom_engine, **additional_params))
pass
elif self.docking_type == 'GeneticAlgorithm':
self.custom_engines.append(GeneticAlgorithm(custom_engine, **additional_params))
Expand Down
56 changes: 45 additions & 11 deletions tests/test_docking.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from numpy.testing import assert_array_less
import oddt
from oddt.docking.dock import Dock
from oddt.docking.MCMCAlgorithm import OptimizationMethod

test_data_dir = os.path.dirname(os.path.abspath(__file__))
receptor = next(oddt.toolkit.readfile('pdb', os.path.join(
Expand All @@ -15,16 +16,49 @@
_ = list(map(lambda x: x.addh(only_polar=True), mols))


def test_genetic_algorithm():
"""Checks, whether genetic algorithm is reducing energy of protein-ligand complex."""
ga_params = {'n_population': 50, 'n_generations': 20, 'top_individuals': 2,
'top_parents': 10, 'crossover_prob': 0.9, 'seed': 123}
# def test_genetic_algorithm():
# """Checks, whether genetic algorithm is minimizing energy of protein-ligand complex."""
# ga_params = {'n_population': 50, 'n_generations': 20, 'top_individuals': 2,
# 'top_parents': 10, 'crossover_prob': 0.9, 'seed': 123}

for scoring_func in ['interaction_energy', 'ri_score']:
docker = Dock(receptor, mols[:5], docking_type='GeneticAlgorithm', additional_params=ga_params)
init_scores = [engine.best_score for engine in docker.custom_engines]
docker.dock()
scores = [score for _, score in docker.output]
# docker = Dock(receptor, mols[:5], docking_type='GeneticAlgorithm', additional_params=ga_params)
# init_scores = [engine.best_score for engine in docker.custom_engines]
# docker.dock()
# scores = [score for _, score in docker.output]

assert_array_less(scores, init_scores, err_msg='Genetic algorithm with {} haven\'t reduced energy of '
'complex'.format(scoring_func))
# for scoring_func in ['interaction_energy', 'ri_score']:
# assert_array_less(scores, init_scores, err_msg='Genetic algorithm with {} haven\'t reduced energy of '
# 'complex'.format(scoring_func))


# def test_mcmc_score_nelder_mead():
# mcmc_params = {'optim': OptimizationMethod.NELDER_MEAD, 'optim_iter': 7, 'mc_steps': 7, 'mut_steps': 100, 'seed': 316815}
# docker = Dock(receptor, mols[:5], docking_type='MCMC', additional_params=mcmc_params)

# init_scores = [engine.engine.score() for engine in docker.custom_engines]
# docker.dock()
# scores = [score for _, score in docker.output]

# assert_array_less(scores, init_scores, err_msg='MCMC algorithm haven\'t reduced energy of complex')


# def test_mcmc_score_simplex():
# mcmc_params = {'optim': OptimizationMethod.SIMPLEX, 'optim_iter': 7, 'mc_steps': 200, 'mut_steps': 100, 'seed': 316815}
# docker = Dock(receptor, mols[:5], docking_type='MCMC', additional_params=mcmc_params)

# init_scores = [engine.engine.score() for engine in docker.custom_engines]
# docker.dock()
# scores = [score for _, score in docker.output]

# assert_array_less(scores, init_scores, err_msg='MCMC algorithm haven\'t reduced energy of complex')


def test_mcmc_score_lbfgsb():
mcmc_params = {'optim': OptimizationMethod.LBFGSB, 'optim_iter': 7, 'mc_steps': 30, 'mut_steps': 100, 'seed': 316815}
docker = Dock(receptor, mols[1], docking_type='MCMC', additional_params=mcmc_params)

init_scores = [engine.engine.score() for engine in docker.custom_engines]
docker.dock()
scores = [score for _, score in docker.output]

assert_array_less(scores, init_scores, err_msg='MCMC algorithm haven\'t reduced energy of complex')

0 comments on commit 615ee9b

Please sign in to comment.