Skip to content

Commit 9ebcd3f

Browse files
authored
Merge pull request #3 from brianjimenez/restraints
Restraints
2 parents 6bdcb82 + 7184367 commit 9ebcd3f

File tree

8 files changed

+153
-12
lines changed

8 files changed

+153
-12
lines changed

bin/simulation/lightdock_setup.py

+28-4
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
from lightdock.util.parser import SetupCommandLineParser
1414
from lightdock.prep.simulation import read_input_structure, save_lightdock_structure, \
1515
calculate_starting_positions, prepare_results_environment, \
16-
create_setup_file, calculate_anm
16+
create_setup_file, calculate_anm, parse_restraints_file, \
17+
get_restraints
1718
from lightdock.constants import DEFAULT_LIGHTDOCK_PREFIX, DEFAULT_ELLIPSOID_DATA_EXTENSION, \
1819
DEFAULT_NMODES_REC, DEFAULT_REC_NM_FILE, DEFAULT_NMODES_LIG, DEFAULT_LIG_NM_FILE
1920
from lightdock.mathutil.ellipsoid import MinimumVolumeEllipsoid
@@ -35,8 +36,8 @@
3536
ligand = read_input_structure(args.ligand_pdb, args.noxt)
3637

3738
# Move structures to origin
38-
receptor.move_to_origin()
39-
ligand.move_to_origin()
39+
rec_translation = receptor.move_to_origin()
40+
lig_translation = ligand.move_to_origin()
4041

4142
# Calculate reference points for receptor
4243
log.info("Calculating reference points for receptor %s..." % args.receptor_pdb)
@@ -63,12 +64,35 @@
6364
calculate_anm(receptor, args.anm_rec, DEFAULT_REC_NM_FILE)
6465
calculate_anm(ligand, args.anm_lig, DEFAULT_LIG_NM_FILE)
6566

67+
# Parse restraints if any:
68+
receptor_restraints = ligand_restraints = None
69+
if args.restraints:
70+
log.info("Reading restraints from %s" % args.restraints)
71+
restraints = parse_restraints_file(args.restraints)
72+
# Check if restraints have been defined for the ligand, but not to the receptor
73+
if len(restraints['ligand']) and not len(restrains['receptor']):
74+
raise LightDockError("Restraints defined for ligand, but not receptor. Try switching structures.")
75+
76+
if not len(restraints['receptor']) and not len(restraints['ligand']):
77+
raise LightDockError("Restraints file specified, but not a single restraint found")
78+
79+
# Check if restraints correspond with real residues
80+
receptor_restraints = get_restraints(receptor, restraints['receptor'])
81+
args.receptor_restraints = restraints['receptor']
82+
ligand_restraints = get_restraints(ligand, restraints['ligand'])
83+
args.ligand_restraints = restraints['ligand']
84+
6685
# Calculate surface points (swarm centers) over receptor structure
6786
starting_points_files = calculate_starting_positions(receptor, ligand,
6887
args.swarms, args.glowworms,
69-
args.starting_points_seed,
88+
args.starting_points_seed,
89+
receptor_restraints, ligand_restraints,
90+
rec_translation, lig_translation,
7091
args.ftdock_file, args.use_anm, args.anm_seed,
7192
args.anm_rec, args.anm_lig)
93+
if len(starting_points_files) != args.swarms:
94+
args.swarms = len(starting_points_files)
95+
log.info('Number of swarms is %d after applying restraints' % args.swarms)
7296

7397
# Create simulation folders
7498
prepare_results_environment(args.swarms)

docs/README.md

+18-2
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ If you execute `lightdock_setup` several options will appear:
5959
```bash
6060
usage: lightdock_setup [-h] [--seed_points STARTING_POINTS_SEED]
6161
[-ft ftdock_file] [--noxt] [-anm] [--seed_anm ANM_SEED]
62-
[-anm_rec ANM_REC] [-anm_lig ANM_LIG]
62+
[-anm_rec ANM_REC] [-anm_lig ANM_LIG] [-rst restraints]
6363
receptor_pdb_file ligand_pdb_file swarms glowworms
6464
lightdock_setup: error: too few arguments
6565

@@ -85,6 +85,7 @@ Below there is a description of the rest of accepted paramenters of `lightdock_s
8585
- **--seed_anm** *ANM_SEED*: An integer can be specified as the seed used in the random number generator of ANM normal modes extents.
8686
- **--anm_rec** *ANM_REC*: The number of non-trivial normal modes calculated for the recepetor in the ANM mode.
8787
- **--anm_lig** *ANM_LIG*: The number of non-trivial normal modes calculated for the ligand in the ANM mode.
88+
- **-rst** *restraints_file*: If `restraints_file` is provided, distance restraints on the receptor surface will be calculated to provided residues. See `2.2. Restraints` section for more information.
8889

8990
### 2.1. Results of the setup
9091

@@ -101,7 +102,22 @@ After the execution of `lightdock_setup` script, several files and directories w
101102
- `*.xyz.npy`: Two files with this extension, one for the receptor and one for the ligand, which contains information about the minimum ellipsoid containing each of the structures in NumPy format.
102103
- `lightdock_rec.nm.npy`and `lightdock_lig.nm.npy`: If ANM is enabled, two files are created containing the normal modes information calculated by the ProDy library.
103104

104-
### 2.2. Tips and tricks
105+
### 2.2. Distance restraints (Experimental)
106+
107+
In version `0.5.1`, distance restraints on the receptor surface have been implemented. This new feature works in the following way:
108+
109+
From a `restraints_file` file containing the following information:
110+
111+
```
112+
R A.SER.150
113+
R A.TYR.151
114+
```
115+
116+
Where `R` means for `Receptor` (at the moment only residue restraints on the receptor are accepted) and then the rest of the line is a residue identifier in the format `Chain.Residue_Name.Residue_Number` in the original PDB file numeration.
117+
118+
For each of these residues, only the closest swarms are considered during `lightdock_setup`. At the moment, only the **10 closest swarms** for each residue are considered. If some of the swarms overlap, then only one copy of that swarm is used.
119+
120+
### 2.3. Tips and tricks
105121

106122
- As a general rule of thumb, the receptor structure is the bigger molecule and the ligand the smaller. With bigger and smaller, the metric used is the longest diameter of the minimum ellipsoid containing the molecule. A script called `lgd_calculate_diameter.py` can be found in `$LIGHTDOCK_HOME/bin/support` path in order to calculate an approximation of that metric.
107123

lightdock/prep/poses.py

+32-1
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
"""Module to prepare initial poses for docking"""
22

33
import os
4+
import operator
45
from lightdock.pdbutil.PDBIO import create_pdb_from_points
56
from lightdock.prep.starting_points import calculate_surface_points
67
from lightdock.prep.ftdock import FTDockCoordinatesParser, classify_ftdock_poses
78
from lightdock.mathutil.lrandom import MTGenerator, NormalGenerator
89
from lightdock.mathutil.cython.quaternion import Quaternion
10+
from lightdock.mathutil.cython.cutil import distance as cdistance
911
from lightdock.constants import CLUSTERS_CENTERS_FILE,\
1012
DEFAULT_PDB_STARTING_PREFIX, DEFAULT_STARTING_PREFIX, DEFAULT_BILD_STARTING_PREFIX, DEFAULT_EXTENT_MU, \
1113
DEFAULT_EXTENT_SIGMA
@@ -51,8 +53,32 @@ def create_file_from_poses(pos_file_name, poses):
5153
positions_file.close()
5254

5355

56+
def apply_restraints(swarm_centers, receptor_restraints, distance_cutoff, translation, swarms_per_restraint=10):
57+
58+
closer_swarms = []
59+
for i, residue in enumerate(receptor_restraints):
60+
distances = {}
61+
ca = residue.get_calpha()
62+
for swarm_id, center in enumerate(swarm_centers):
63+
distances[swarm_id] = cdistance(ca.x + translation[0], ca.y + translation[1], ca.z + translation[2],
64+
center[0], center[1], center[2])
65+
sorted_distances = sorted(distances.items(), key=operator.itemgetter(1))
66+
swarms_considered = 0
67+
for swarm in sorted_distances:
68+
swarm_id, distance = swarm[0], swarm[1]
69+
if distance <= distance_cutoff:
70+
closer_swarms.append(swarm_id)
71+
swarms_considered += 1
72+
if swarms_considered == swarms_per_restraint:
73+
break
74+
closer_swarms = list(set(closer_swarms))
75+
return closer_swarms
76+
77+
5478
def calculate_initial_poses(receptor, ligand, num_clusters, num_glowworms,
55-
seed, dest_folder, ftdock_file='', nm_mode=False, nm_seed=0, rec_nm=0, lig_nm=0):
79+
seed, receptor_restraints, ligand_restraints,
80+
rec_translation, lig_translation,
81+
dest_folder, ftdock_file='', nm_mode=False, nm_seed=0, rec_nm=0, lig_nm=0):
5682
"""Calculates the starting points for each of the glowworms using the center of clusters
5783
and FTDock poses.
5884
"""
@@ -70,6 +96,11 @@ def calculate_initial_poses(receptor, ligand, num_clusters, num_glowworms,
7096
ligand,
7197
num_clusters,
7298
distance_step=1.0)
99+
# Filter cluster centers far from the restraints
100+
if receptor_restraints:
101+
filtered_swarms = apply_restraints(cluster_centers, receptor_restraints, ligand_diameter / 2., rec_translation)
102+
cluster_centers = [cluster_centers[i] for i in filtered_swarms]
103+
73104
pdb_file_name = os.path.join(dest_folder, CLUSTERS_CENTERS_FILE)
74105
create_pdb_from_points(pdb_file_name, cluster_centers)
75106

lightdock/prep/simulation.py

+57-4
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,9 @@ def check_starting_file(file_name, glowworms, use_anm, anm_rec, anm_lig):
9292
return num_glowworms == glowworms
9393

9494

95-
def calculate_starting_positions(receptor, ligand, swarms, glowworms, starting_points_seed,
96-
ftdock_file=None, use_anm=False, anm_seed=0, anm_rec=DEFAULT_NMODES_REC, anm_lig=DEFAULT_NMODES_LIG):
95+
def calculate_starting_positions(receptor, ligand, swarms, glowworms, starting_points_seed,
96+
receptor_restraints, ligand_restraints, rec_translation, lig_translation, ftdock_file=None,
97+
use_anm=False, anm_seed=0, anm_rec=DEFAULT_NMODES_REC, anm_lig=DEFAULT_NMODES_LIG):
9798
"""Defines the starting positions of each glowworm in the simulation.
9899
99100
If the init_folder already exists, uses the starting positions from this folder.
@@ -104,12 +105,19 @@ def calculate_starting_positions(receptor, ligand, swarms, glowworms, starting_p
104105
os.mkdir(init_folder)
105106
starting_points_files = calculate_initial_poses(receptor, ligand,
106107
swarms, glowworms,
107-
starting_points_seed, init_folder,
108+
starting_points_seed,
109+
receptor_restraints, ligand_restraints,
110+
rec_translation, lig_translation,
111+
init_folder,
108112
ftdock_file, use_anm,
109113
anm_seed, anm_rec, anm_lig)
110114
log.info("Generated %d positions files" % len(starting_points_files))
111115
else:
112-
log.warning("Folder %s already exists, skipping calculation" % init_folder)
116+
if receptor_restraints:
117+
log.warning("Folder %s already exists and restraints apply. Check for consistency, possible error!" % init_folder)
118+
else:
119+
log.warning("Folder %s already exists, skipping calculation" % init_folder)
120+
113121
pattern = os.path.join(DEFAULT_POSITIONS_FOLDER, "%s*.dat" % DEFAULT_STARTING_PREFIX)
114122
starting_points_files = glob.glob(pattern)
115123
if len(starting_points_files) != swarms:
@@ -197,3 +205,48 @@ def get_default_box(use_anm, anm_rec, anm_lig):
197205
boundaries.extend([Boundary(MIN_EXTENT, MAX_EXTENT) for _ in xrange(anm_lig)])
198206

199207
return BoundingBox(boundaries)
208+
209+
210+
def parse_restraints_file(restraints_file_name):
211+
with open(restraints_file_name) as input_restraints:
212+
raw_restraints = [line.rstrip(os.linesep) for line in input_restraints.readlines()]
213+
restraints = {'receptor': [], 'ligand': []}
214+
for restraint in raw_restraints:
215+
if restraint and restraint[0] in ['R', 'L']:
216+
try:
217+
fields = restraint[2:].split('.')
218+
# Only consider first character if many
219+
chain_id = fields[0][0].upper()
220+
# Only considering 3 chars if many
221+
residue = fields[1][0:3].upper()
222+
# Check for integer
223+
residue_number = int(fields[2])
224+
parsed_restraint = "%s.%s.%d" % (chain_id, residue, residue_number)
225+
if restraint[0] == 'R':
226+
restraints['receptor'].append(parsed_restraint)
227+
else:
228+
restraints['ligand'].append(parsed_restraint)
229+
except:
230+
log.warning('Ignoring restraints %s' % restraint)
231+
232+
# Make unique the restraints:
233+
restraints['receptor'] = list(set(restraints['receptor']))
234+
restraints['ligand'] = list(set(restraints['ligand']))
235+
236+
return restraints
237+
238+
239+
def get_restraints(structure, restraints):
240+
"""Check for each restraint in the format Chain.ResidueName.ResidueNumber in
241+
restraints if they exist in the given structure.
242+
"""
243+
residues = []
244+
for restraint in restraints:
245+
chain_id, residue_name, residue_number = restraint.split('.')
246+
residue = structure.get_residue(chain_id, residue_name, residue_number)
247+
if not residue:
248+
raise LightDockError("Restraint %s not found in structure" % restraint)
249+
else:
250+
residues.append(residue)
251+
return residues
252+

lightdock/structure/complex.py

+9
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,15 @@ def move_to_origin(self):
108108
"""Moves the structure to the origin of coordinates"""
109109
translation = [-1*c for c in self.center_of_coordinates()]
110110
self.translate(translation)
111+
return translation
112+
113+
def get_residue(self, chain_id, residue_name, residue_number):
114+
for chain in self.chains:
115+
if chain_id == chain.cid:
116+
for residue in chain.residues:
117+
if residue.name == residue_name and int(residue.number) == int(residue_number):
118+
return residue
119+
return None
111120

112121
def __getitem__(self, item):
113122
return self.atom_coordinates[item]

lightdock/structure/residue.py

+4
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ def get_atom(self, atom_name):
8585
return atom
8686
return None
8787

88+
def get_calpha(self):
89+
"""Get the Calpha atom"""
90+
return self.get_atom('CA')
91+
8892
def get_non_hydrogen_atoms(self):
8993
return [atom for atom in self.atoms if not atom.is_hydrogen()]
9094

lightdock/util/parser.py

+4
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ def __init__(self):
9797
parser.add_argument("-anm_lig", "--anm_lig", help="Number of ANM modes for ligand",
9898
type=SetupCommandLineParser.valid_integer_number,
9999
dest="anm_lig", default=DEFAULT_NMODES_LIG)
100+
# Restraints file
101+
parser.add_argument("-rst", "--rst", help="Restraints file",
102+
dest="restraints", type=CommandLineParser.valid_file,
103+
metavar="restraints", default=None)
100104

101105
self.args = parser.parse_args()
102106

lightdock/version.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""Framework version"""
22

3-
CURRENT_VERSION = "0.5.0"
3+
CURRENT_VERSION = "0.5.1"

0 commit comments

Comments
 (0)