1414import logging
1515import sys
1616from 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
1820from pathlib import Path
19- from os import mkdir , chdir
21+ from platform import platform
2022from shutil import copy
2123from time import time
22- from dataclasses import dataclass , field
23- from multiprocessing import Pool
2424
2525import ccdc
26- from ccdc .io import EntryReader
2726from 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:
3631CONF_FILE = 'gold.conf'
3732
38- # Default number of parallel processes to use...
39-
33+ # Default number of parallel processes:
4034N_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
5648class 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
7362SCRIPT_INFO = f"""
7463Script: { sys .argv [0 ]}
7564Platform: { platform ()}
@@ -78,7 +67,6 @@ def __post_init__(self):
7867CSD API version: { ccdc .__version__ }
7968"""
8069
81- ########################################################################################################################
8270
8371def 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
143121def 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
280221if __name__ == '__main__' :
281-
282222 main ()
283-
284- ########################################################################################################################
285- # End
286- ########################################################################################################################
0 commit comments