Skip to content

Commit 758d1a8

Browse files
committed
Updated gold_multi script for clarity in comments. Changed to PEP8 throughout
1 parent 05d9733 commit 758d1a8

1 file changed

Lines changed: 61 additions & 125 deletions

File tree

scripts/discovery/gold_multi/gold_multi.py

Lines changed: 61 additions & 125 deletions
Original file line numberDiff line numberDiff line change
@@ -14,47 +14,39 @@
1414
import logging
1515
import sys
1616
from argparse import ArgumentParser
17-
from platform import platform
17+
from dataclasses import dataclass, field
18+
from multiprocessing import Pool
19+
from os import chdir, mkdir
1820
from pathlib import Path
19-
from os import mkdir, chdir
21+
from platform import platform
2022
from shutil import copy
2123
from time import time
22-
from dataclasses import dataclass, field
23-
from multiprocessing import Pool
2424

2525
import ccdc
26-
from ccdc.io import EntryReader
2726
from ccdc.docking import Docker
27+
from ccdc.io import EntryReader
2828

29-
########################################################################################################################
30-
#
31-
# Program parameters...
32-
#
33-
34-
# Default GOLD conf file to use...
3529

30+
# Default GOLD conf file:
3631
CONF_FILE = 'gold.conf'
3732

38-
# Default number of parallel processes to use...
39-
33+
# Default number of parallel processes:
4034
N_PROCESSES = 6
4135

42-
# Note that, although the number of batches is currently the same as the number of processes, it could in theory
43-
# be greater. One reason to do this might be to ensure that a contiguous block of large, flexible molecules
44-
# in the input file don't make one batch run very much slower than the others. There is a cost to starting up
45-
# new instances of GOLD, however, so care should be taken with this.
46-
#
47-
# The chunking is done such that the sizes are as even as possible, in that the last one will not be much shorter than
48-
# the others. For example, for [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] split into 3 batches, we want
49-
# [[1, 2, 3, 4], [4, 5, 6], [6, 7, 8, 9]] and not [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10]].
36+
# Note, although the number of batches is currently the same as the number of processes, it could in theory
37+
# be greater. One reason to do this might be to ensure that a contiguous block of large, flexible molecules
38+
# in the input file don't make one batch run much slower than the others. However, there is a cost to starting
39+
# up new instances of GOLD, so care should be taken with this.
5040

51-
########################################################################################################################
41+
# The batching is done such that the sizes are as even as possible.
42+
# So the last one will not be much shorter than the others.
43+
# For example, for [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] split into 3 batches, we want
44+
# [[1, 2, 3, 4], [4, 5, 6], [6, 7, 8, 9]] and not [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10]].
5245

53-
# Record type to hold the parameters defining a batch...
5446

5547
@dataclass
5648
class Batch:
57-
49+
"""Record type to hold the parameters defining a batch"""
5850
n: int # Batch number
5951
start: int # Index of first molecule in batch
6052
finish: int # Index of last molecule in batch
@@ -63,13 +55,10 @@ class Batch:
6355
dir: Path = field(init=False) # Sub-directory for batch, see __post_init__ below
6456

6557
def __post_init__(self):
58+
self.dir = self.output_dir / f'batch_{self.n:02d}'
6659

67-
self.dir = self.output_dir / f'chunk_{self.n:02d}' # Directory will be created in do_chunk function
68-
69-
########################################################################################################################
70-
71-
# A summary of information about the script and where it is running, useful for debugging etc...
7260

61+
# A summary of information about the script and where it is running, useful for debugging etc
7362
SCRIPT_INFO = f"""
7463
Script: {sys.argv[0]}
7564
Platform: {platform()}
@@ -78,7 +67,6 @@ def __post_init__(self):
7867
CSD API version: {ccdc.__version__}
7968
"""
8069

81-
########################################################################################################################
8270

8371
def get_logger(name=__name__):
8472
logger = logging.getLogger(name)
@@ -89,7 +77,7 @@ def get_logger(name=__name__):
8977
return logger
9078

9179

92-
def do_batch(batch):
80+
def do_batch(batch: Batch) -> int:
9381

9482
"""
9583
Dock a batch of the input file.
@@ -102,61 +90,52 @@ def do_batch(batch):
10290

10391
logger = get_logger(f"Batch {batch.n}")
10492

105-
######
106-
107-
# As Settings objects cannot be pickled they cannot be passed to pool processes, so we create a fresh copy...
108-
93+
# Settings objects cannot be pickled, so they cannot be passed to pool processes
94+
# requiring a fresh copy:
10995
settings = Docker.Settings().from_file(str(batch.conf_file))
11096

111-
# Create and enter the sub-directory for this batch...
112-
97+
# Create and enter the sub-directory for this batch:
11398
mkdir(batch.dir)
114-
11599
chdir(batch.dir)
116100

117-
settings.output_directory = '.' # Ensure GOLD writes output to the batch sub-directory
118-
119-
# Specify the batch of molecules to dock...
120-
121-
ligand_file = settings.ligand_files[0] # The ligand file info will be overwritten, so store for reference below
101+
# Ensure GOLD writes output to the batch sub-directory
102+
settings.output_directory = '.'
122103

104+
# Specify the batch of molecules to dock
105+
# The ligand file info will be overwritten, so store for reference below
106+
ligand_file = settings.ligand_files[0]
123107
settings.clear_ligand_files()
124-
125108
settings.add_ligand_file(ligand_file.file_name, ndocks=ligand_file.ndocks, start=batch.start, finish=batch.finish)
126109

127-
# Run docking...
128-
110+
# Run docking
129111
logger.info(f"Starting (indices {batch.start} - {batch.finish})...")
130112

131113
docker = Docker(settings=settings)
132-
133114
results = docker.dock()
134115

135116
logger.info(f"...done")
136117

137-
# As we can't return the results (as they are not picklable) and the poses have already been written to disk, we just return the status code
138-
139118
return results.return_code
140119

141-
########################################################################################################################
142120

143121
def main():
144-
145-
"""
146-
Dock the molecules from the supplied input file in parallel.
147-
"""
122+
"""Dock the molecules from the supplied input file in parallel."""
148123

149124
logger = get_logger('Main')
150125

151126
parser = ArgumentParser()
152127

153-
parser.add_argument('conf_file', nargs='?', default=CONF_FILE, type=str, help=f"GOLD configuration file (default='{CONF_FILE}')")
154-
parser.add_argument('--n_processes', default=N_PROCESSES, type=int, help=f"No. of processes (default={N_PROCESSES})")
155-
128+
parser.add_argument(
129+
'conf_file', nargs='?', default=CONF_FILE, type=str,
130+
help=f"GOLD configuration file (default='{CONF_FILE}')"
131+
)
132+
parser.add_argument(
133+
'--n_processes', default=N_PROCESSES, type=int,
134+
help=f"No. of processes (default={N_PROCESSES})"
135+
)
156136
config = parser.parse_args()
157137

158138
conf_file = Path(config.conf_file)
159-
160139
if not conf_file.exists():
161140
logger.error(f"Error! Configuration file '{conf_file}' not found!", file=sys.stderr)
162141
sys.exit(1)
@@ -171,116 +150,73 @@ def main():
171150

172151
t0 = time()
173152

174-
######
175-
176-
# Load setting from GOLD conf file...
177-
178153
settings = Docker.Settings().from_file(str(conf_file))
179154

180-
# Ensure the output directory exists (a sub-directory for each batch is created within it)...
181-
155+
# Ensure the output directory exists (a sub-directory for each batch is created within)
182156
output_dir = Path(settings.output_directory)
183157

184-
if not str(output_dir) == '.': # Skip directory (re)creation if output dir is current directory
185-
158+
# Skip directory (re)creation if output dir is current directory
159+
if not str(output_dir) == '.':
186160
if output_dir.exists():
187-
188161
logger.error(f"Error! Output dir '{output_dir}' already exists.")
189-
190162
sys.exit(1)
191-
192163
mkdir(output_dir)
193164

194-
# Count the molecules to dock in the input file...
195-
165+
# Count the molecules to dock in the input file
196166
input_file = Path(settings.ligand_files[0].file_name)
197167

198168
with EntryReader(str(input_file)) as reader:
199-
200169
n_molecules = len(reader)
201-
202170
logger.info(f"There are {n_molecules} molecules to dock on {n_processes} processes...")
203171

204-
######
205-
206172
# Here we determine the sets of parameters defining the batches; recall that:
207173
# 1) We choose the number of batches to be the same as the number of processes.
208174
# 2) GOLD uses 1-based indexing for molecules.
209175

210176
basic_size, remainder = n_molecules // n_processes, n_molecules % n_processes
211177

212-
batches, start = [], 1
213-
214-
for chunk_n in range(1, n_processes+1):
215-
216-
finish = start + basic_size + (1 if chunk_n <= remainder else 0) - 1
217-
218-
batches.append(Batch(n=chunk_n, start=start, finish=finish, conf_file=conf_file, output_dir=output_dir))
178+
batches = []
179+
start = 1
219180

181+
for batch_n in range(1, n_processes+1):
182+
finish = start + basic_size + (1 if batch_n <= remainder else 0) - 1
183+
batches.append(Batch(n=batch_n, start=start, finish=finish, conf_file=conf_file, output_dir=output_dir))
220184
start = finish + 1
221185

222-
######
223-
224-
# Dock the batches in parallel...
225-
186+
# Dock the batches in parallel
226187
with Pool(n_processes) as pool:
227-
228188
_ = pool.map(do_batch, batches) # We are not currently checking the return codes
229189

230-
######
231-
232-
# Combine output from batches into output directory and write combined 'bestranking.lst' file...
233-
234-
bestranking, preamble_and_header = [], None # Records and preamble plus data header from individual batch 'bestranking.lst' files
235-
190+
# Combine output from batches into output directory and write combined 'bestranking.lst' file
191+
bestranking = []
192+
preamble_and_header = None
236193
for batch in batches:
237-
238-
# Copy solution files...
239-
194+
# Copy solution files
240195
for soln_file in batch.dir.glob('gold_soln_*'):
241-
242196
copy(str(soln_file), output_dir)
243197

244-
# Load records from 'bestranking.lst' file, fixing file paths as we go...
245-
198+
# Load records from 'bestranking.lst' file, fixing file paths as we go
246199
with (batch.dir / 'bestranking.lst').open('r') as file:
247-
248200
lines = [x.replace(str(batch.dir), str(output_dir)).replace('\\.\\', '\\') for x in file.read().split('\n')]
249201

250-
if batch.n == 1: # Take preamble and data header from first batch only
251-
202+
# Take preamble and data header from first batch only
203+
if batch.n == 1:
252204
preamble_and_header = lines[:7]
253205

254206
bestranking.extend(lines[7:-1]) # Records
255207

256-
# Write 'bestranking.lst' file...
257-
258-
with (output_dir / 'bestranking.lst').open('w') as file:
259-
260-
file.write('\n'.join(preamble_and_header) + '\n') # Preamble and data header
261-
262-
file.write('\n'.join(bestranking) + '\n') # Records
263-
264-
# Copy over any other required files from first batch directory...
265-
266-
chunk_dir = batches[0].dir
208+
# Write 'bestranking.lst' file
209+
with open(output_dir / 'bestranking.lst', 'w') as file:
210+
file.write(f'\n{preamble_and_header}\n') # Preamble and data header
211+
file.write(f'\n{bestranking}\n') # Records
267212

213+
# Copy over any other required files from first batch directory
214+
batch_dir = batches[0].dir
268215
for file_name in ['gold_protein.mol2']:
269-
270-
copy(str(chunk_dir / file_name), output_dir)
271-
272-
######
273-
274-
# All done.
216+
copy(str(batch_dir / file_name), output_dir)
275217

276218
logger.info(f"Finished in {time() - t0:.1f} seconds.")
277219

278-
########################################################################################################################
279220

280221
if __name__ == '__main__':
281-
282222
main()
283-
284-
########################################################################################################################
285-
# End
286-
########################################################################################################################

0 commit comments

Comments
 (0)