vantami 0.4.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vantami/__init__.py +12 -0
- vantami/api/__init__.py +0 -0
- vantami/api/convert.py +45 -0
- vantami/api/resolve.py +290 -0
- vantami/cache.py +14 -0
- vantami/chemistry/__init__.py +0 -0
- vantami/chemistry/molecule.py +273 -0
- vantami/cli/CDDD.py +63 -0
- vantami/cli/CIR.py +185 -0
- vantami/cli/Dock.py +215 -0
- vantami/cli/MolStandardizer.py +635 -0
- vantami/cli/Mordred.py +76 -0
- vantami/cli/OptunaOptimization.py +151 -0
- vantami/cli/__init__.py +0 -0
- vantami/data/__init__.py +0 -0
- vantami/data/cluster.py +230 -0
- vantami/data/descriptors.py +1076 -0
- vantami/data/distance.py +539 -0
- vantami/data/filter.py +69 -0
- vantami/data/manager.py +309 -0
- vantami/data/manipulate.py +94 -0
- vantami/data/partition.py +659 -0
- vantami/data/transform.py +606 -0
- vantami/deep/__init__.py +5 -0
- vantami/deep/dataset.py +252 -0
- vantami/deep/loader.py +172 -0
- vantami/deep/models.py +625 -0
- vantami/deep/modules.py +704 -0
- vantami/deep/utils.py +28 -0
- vantami/deep/vectorizer.py +389 -0
- vantami/io/__init__.py +2 -0
- vantami/io/database.py +303 -0
- vantami/io/file.py +99 -0
- vantami/metrics/__init__.py +0 -0
- vantami/metrics/modellability.py +30 -0
- vantami/ml/__init__.py +0 -0
- vantami/ml/augood.py +227 -0
- vantami/ml/evaluate.py +151 -0
- vantami/ml/models.py +617 -0
- vantami/ml/optimize.py +319 -0
- vantami/ml/params.py +331 -0
- vantami/ml/score.py +170 -0
- vantami/ml/select.py +150 -0
- vantami/ml/utils.py +125 -0
- vantami/nlp/__init__.py +0 -0
- vantami/nlp/article.py +197 -0
- vantami/nlp/cluster.py +32 -0
- vantami/nlp/tokenize.py +60 -0
- vantami/standardize/__init__.py +0 -0
- vantami/standardize/clean.py +853 -0
- vantami/standardize/duplicates.py +338 -0
- vantami/standardize/filter.py +699 -0
- vantami/standardize/validate.py +105 -0
- vantami/stats/__init__.py +0 -0
- vantami/stats/tests.py +111 -0
- vantami/stats/utils.py +15 -0
- vantami/visualize/__init__.py +0 -0
- vantami/visualize/ecdf.py +79 -0
- vantami/visualize/embedding.py +93 -0
- vantami/visualize/performance.py +26 -0
- vantami/visualize/predictions.py +352 -0
- vantami/visualize/properties.py +322 -0
- vantami/visualize/utils.py +241 -0
- vantami-0.4.3.dist-info/METADATA +143 -0
- vantami-0.4.3.dist-info/RECORD +68 -0
- vantami-0.4.3.dist-info/WHEEL +5 -0
- vantami-0.4.3.dist-info/licenses/LICENSE +28 -0
- vantami-0.4.3.dist-info/top_level.txt +1 -0
vantami/cli/CDDD.py
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
""" Wrapper for CDDD.py due to requirement of specific python environment """
|
|
2
|
+
import argparse
|
|
3
|
+
import pandas as pd
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pickle
|
|
6
|
+
from cddd.inference import InferenceModel
|
|
7
|
+
from typing import Union, List
|
|
8
|
+
|
|
9
|
+
parser = argparse.ArgumentParser(prog='CDDD', description='Calculate CDDD descriptors from SMILES')
|
|
10
|
+
|
|
11
|
+
parser.add_argument('-i', '--input', help='Path to an input file. Must be a .csv file.')
|
|
12
|
+
parser.add_argument('-o', '--output', help='Path to an output file. Must be a .pkl file.')
|
|
13
|
+
parser.add_argument('-s', '--smiles_col', help='Name of the column holding the SMILES')
|
|
14
|
+
parser.add_argument('-d', '--descriptor_col', help='Name of the column to which write the descriptors')
|
|
15
|
+
parser.add_argument('-n', '--n_cpu', help='Number of CPU threads to use during calculations')
|
|
16
|
+
parser.add_argument('-m', '--model', help='Path to the default_model of CDDD')
|
|
17
|
+
|
|
18
|
+
parsed_args = parser.parse_args()
|
|
19
|
+
|
|
20
|
+
def main(args):
|
|
21
|
+
|
|
22
|
+
input_ = args.input # .csv file
|
|
23
|
+
output_ = args.output # .pkl file
|
|
24
|
+
smiles_col_ = args.smiles_col
|
|
25
|
+
descriptor_col_ = args.descriptor_col
|
|
26
|
+
n_cpu = int(args.n_cpu)
|
|
27
|
+
model_path = args.model
|
|
28
|
+
|
|
29
|
+
model = InferenceModel(model_path, use_gpu=False, cpu_threads=n_cpu)
|
|
30
|
+
|
|
31
|
+
df = pd.read_csv(input_, sep='\t')
|
|
32
|
+
# unpack lists
|
|
33
|
+
df.loc[:, smiles_col_] = df[smiles_col_].apply(lambda entry: entry.split(' : ') if ' : ' in entry else entry)
|
|
34
|
+
|
|
35
|
+
def process(smiles: Union[str, List[str]], model_):
|
|
36
|
+
|
|
37
|
+
if isinstance(smiles, str):
|
|
38
|
+
try:
|
|
39
|
+
fp = model_.seq_to_emb(smiles)[0]
|
|
40
|
+
return fp
|
|
41
|
+
|
|
42
|
+
except Exception as e:
|
|
43
|
+
print(f'Could not process < {smiles} > due to the following error:\n{e}')
|
|
44
|
+
return np.nan
|
|
45
|
+
|
|
46
|
+
if isinstance(smiles, list) & all([isinstance(smi, str) for smi in smiles]):
|
|
47
|
+
try:
|
|
48
|
+
fps = [model_.seq_to_emb(smi)[0] for smi in smiles]
|
|
49
|
+
return fps
|
|
50
|
+
|
|
51
|
+
except Exception as e:
|
|
52
|
+
print(f'Unable to process < {smiles} > due to: \n{e}')
|
|
53
|
+
return np.nan
|
|
54
|
+
|
|
55
|
+
print(f'Expected < smiles > argument to be of type str or List[str], received < {type(smiles)} > instead.')
|
|
56
|
+
return np.nan
|
|
57
|
+
|
|
58
|
+
df[descriptor_col_] = df[smiles_col_].apply(process, model_=model)
|
|
59
|
+
pickle.dump(df, open(output_, 'wb'), protocol=4)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
if __name__ == '__main__':
|
|
63
|
+
main(parsed_args)
|
vantami/cli/CIR.py
ADDED
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import numpy as np
|
|
3
|
+
import pandas as pd
|
|
4
|
+
from urllib.request import urlopen
|
|
5
|
+
from urllib.error import HTTPError, URLError
|
|
6
|
+
import time
|
|
7
|
+
import rdkit
|
|
8
|
+
from rdkit.Chem import AllChem
|
|
9
|
+
from rdkit import RDLogger
|
|
10
|
+
|
|
11
|
+
parser = argparse.ArgumentParser(prog='ChemicalIdentityResolver',
|
|
12
|
+
description='Uses NCI/NIH CIR to find SMILES for a given name')
|
|
13
|
+
|
|
14
|
+
parser.add_argument('-i', '--input', help='Absolute path to an input file')
|
|
15
|
+
parser.add_argument('-o', '--output', help='Absolute path to an output file')
|
|
16
|
+
parser.add_argument('-n', '--name', help="Name of the column holding molecule's names")
|
|
17
|
+
parser.add_argument('-r', '--resolver', help='Type of resolved identity')
|
|
18
|
+
|
|
19
|
+
parsed_args = parser.parse_args()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def main(args):
|
|
23
|
+
"""
|
|
24
|
+
TODO: add modules for using common chemistry CAS resolver
|
|
25
|
+
TODO: add modules for using ECHA website for resolving names/CAS
|
|
26
|
+
Resolves chemical identities representations using the NCI/NIH CIR API.
|
|
27
|
+
Accepted inputs and outputs are: .xlsx, .csv, .parquet, .pkl.
|
|
28
|
+
Accepted keywords for resolver are: smiles, stdinchi, stdinchikey, cas, iupac_name
|
|
29
|
+
|
|
30
|
+
Example of usage:
|
|
31
|
+
|
|
32
|
+
python3 CIR.py --input data.csv --output data_out.csv --name molecules --resolver smiles --strip_salts True
|
|
33
|
+
|
|
34
|
+
Parameters
|
|
35
|
+
----------
|
|
36
|
+
args : argparse.Namespace
|
|
37
|
+
Parsed arguments from command line.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def chemical_resolver(name: str, resolver: str = 'smiles', sleep_time: int = 6, max_retries: int = 5):
|
|
41
|
+
"""
|
|
42
|
+
Resolve chemical identity a single molecule using NCI/NIH CIR API
|
|
43
|
+
|
|
44
|
+
Parameters
|
|
45
|
+
---------------
|
|
46
|
+
name : str
|
|
47
|
+
name of a molecule
|
|
48
|
+
resolver : str
|
|
49
|
+
type of output. available are: smiles, stdinchi, stdinchikey, cas, iupac_name
|
|
50
|
+
sleep_time : int
|
|
51
|
+
time to wait between attempting a retry
|
|
52
|
+
max_retries : int
|
|
53
|
+
maximum number of attempts to make
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
name = name.lower().strip().replace(' ', '%20')
|
|
57
|
+
url = f'https://cactus.nci.nih.gov/chemical/structure/{name.lower()}/{resolver.lower()}'
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
out = urlopen(url, data=None, timeout=8).read().decode()
|
|
61
|
+
return out
|
|
62
|
+
except HTTPError as e:
|
|
63
|
+
print(f'Failed to resolve "{name.replace("%20", " ")}" due to {e}')
|
|
64
|
+
try:
|
|
65
|
+
out = urlopen(url+'?resolver=name_by_chemspider', data=None, timeout=16).read().decode()
|
|
66
|
+
return out
|
|
67
|
+
except HTTPError as e:
|
|
68
|
+
print(f'Failed to resolve "{name.replace("%20", " ")}" due to {e}')
|
|
69
|
+
return np.nan
|
|
70
|
+
except Exception:
|
|
71
|
+
return np.nan
|
|
72
|
+
except URLError or ConnectionResetError as e:
|
|
73
|
+
print(f'Connection lost while resolving "{name.replace("%20", " ")}" due to {e}. '
|
|
74
|
+
f'Retrying in {sleep_time} seconds.')
|
|
75
|
+
retries = 1
|
|
76
|
+
while retries <= max_retries:
|
|
77
|
+
try:
|
|
78
|
+
time.sleep(sleep_time)
|
|
79
|
+
sleep_time += 2
|
|
80
|
+
out = urlopen(url, data=None).read().decode()
|
|
81
|
+
print(f'"{name.replace("%20", " ")}" solved successfully')
|
|
82
|
+
return out
|
|
83
|
+
except HTTPError as e:
|
|
84
|
+
print(f'Failed to resolve "{name.replace("%20", " ")}" after {retries} attempts due to {e}')
|
|
85
|
+
return np.nan
|
|
86
|
+
except URLError or ConnectionResetError as e:
|
|
87
|
+
print(f'Connection lost while resolving "{name.replace("%20", " ")}" due to {e}. '
|
|
88
|
+
f'Retrying in {sleep_time} seconds.')
|
|
89
|
+
pass
|
|
90
|
+
retries += 1
|
|
91
|
+
print(f'Failed to resolve "{name.replace("%20", " ")}" after {max_retries} attempts')
|
|
92
|
+
return np.nan
|
|
93
|
+
except UnicodeEncodeError:
|
|
94
|
+
print(f'Could not properly encode {name}. Skipping.')
|
|
95
|
+
return np.nan
|
|
96
|
+
|
|
97
|
+
def validate_smiles(df: pd.DataFrame, smiles_col: str = 'SMILES'):
|
|
98
|
+
|
|
99
|
+
RDLogger.DisableLog('rdApp.*')
|
|
100
|
+
|
|
101
|
+
"""
|
|
102
|
+
Use RDKit to check if a SMILES can be converted to a valid molecule and check it for potential errors.
|
|
103
|
+
|
|
104
|
+
Parameters
|
|
105
|
+
---------------
|
|
106
|
+
df : pd.DataFrame
|
|
107
|
+
with SMILES
|
|
108
|
+
smiles_col : str
|
|
109
|
+
Column in df holding SMILES of molecules
|
|
110
|
+
"""
|
|
111
|
+
|
|
112
|
+
def rdkit_validate(smiles: str):
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
mol = rdkit.Chem.MolFromSmiles(smiles, sanitize=True)
|
|
116
|
+
if mol is not None:
|
|
117
|
+
return True, None
|
|
118
|
+
else:
|
|
119
|
+
mol = rdkit.Chem.MolFromSmiles(smiles, sanitize=False)
|
|
120
|
+
if mol is not None:
|
|
121
|
+
rdkit_problems = [problem.GetType() for problem in rdkit.Chem.DetectChemistryProblems(mol)]
|
|
122
|
+
return False, rdkit_problems
|
|
123
|
+
else:
|
|
124
|
+
return False, 'Invalid molecule'
|
|
125
|
+
except Exception as e:
|
|
126
|
+
return False, 'Invalid molecule'
|
|
127
|
+
|
|
128
|
+
out = df[smiles_col].apply(rdkit_validate).tolist()
|
|
129
|
+
|
|
130
|
+
df['Correct'] = [x[0] for x in out]
|
|
131
|
+
df['Issues'] = [x[1] for x in out]
|
|
132
|
+
|
|
133
|
+
return df
|
|
134
|
+
|
|
135
|
+
input_ = args.input
|
|
136
|
+
output_ = args.output
|
|
137
|
+
name_ = args.name
|
|
138
|
+
resolver_ = args.resolver
|
|
139
|
+
|
|
140
|
+
if resolver_ not in ['smiles', 'stdinchi', 'stdinchikey', 'cas', 'iupac_name']:
|
|
141
|
+
raise ValueError('Wrong format passed. Expected one of: smiles, stdinchi, stdinchikey, cas, iupac_name')
|
|
142
|
+
|
|
143
|
+
in_ext = input_.split('.')[-1]
|
|
144
|
+
out_ext = output_.split('.')[-1]
|
|
145
|
+
|
|
146
|
+
if in_ext == 'xlsx':
|
|
147
|
+
data = pd.read_excel(input_)
|
|
148
|
+
elif in_ext == 'csv':
|
|
149
|
+
data = pd.read_csv(input_)
|
|
150
|
+
elif in_ext == 'parquet':
|
|
151
|
+
data = pd.read_parquet(input_)
|
|
152
|
+
elif in_ext == 'pkl':
|
|
153
|
+
data = pd.read_pickle(input_)
|
|
154
|
+
else:
|
|
155
|
+
raise ValueError(f'Unrecognized file extension. Expected one of: .xlsx, .csv, .parquet, .pkl')
|
|
156
|
+
|
|
157
|
+
data['Mol_ID'] = [x for x in range(len(data))]
|
|
158
|
+
data['Output'] = data[name_].apply(chemical_resolver, resolver=resolver_)
|
|
159
|
+
|
|
160
|
+
solved = data.dropna(subset='Output', axis=0)
|
|
161
|
+
unsolved = data.drop(index=solved.index)
|
|
162
|
+
|
|
163
|
+
write_path = output_.rstrip(f'.{out_ext}')
|
|
164
|
+
|
|
165
|
+
if str(resolver_) == 'smiles':
|
|
166
|
+
solved = validate_smiles(solved, 'Output')
|
|
167
|
+
|
|
168
|
+
if out_ext == 'xlsx':
|
|
169
|
+
solved.to_excel(f'{write_path}_resolved.xlsx', index=False)
|
|
170
|
+
unsolved.to_excel(f'{write_path}_unsolved.xlsx', index=False)
|
|
171
|
+
elif out_ext == 'csv':
|
|
172
|
+
solved.to_csv(f'{write_path}_resolved.csv', header=True, index=False)
|
|
173
|
+
unsolved.to_csv(f'{write_path}_unsolved.csv', header=True, index=False)
|
|
174
|
+
elif out_ext == 'parquet':
|
|
175
|
+
solved.to_parquet(f'{write_path}_resolved.parquet')
|
|
176
|
+
unsolved.to_parquet(f'{write_path}_unsolved.parquet')
|
|
177
|
+
elif out_ext == 'pkl':
|
|
178
|
+
solved.to_pickle(f'{write_path}_resolved.pkl')
|
|
179
|
+
unsolved.to_pickle(f'{write_path}_unsolved.pkl')
|
|
180
|
+
else:
|
|
181
|
+
raise ValueError(f'Unrecognized file extension. Expected one of: .xlsx, .csv, .parquet, .pkl')
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
if __name__ == '__main__':
|
|
185
|
+
main(parsed_args)
|
vantami/cli/Dock.py
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"""
|
|
2
|
+
version = 0.1.0
|
|
3
|
+
date = 25.06.2024
|
|
4
|
+
author = M-Iwan
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import argparse
|
|
8
|
+
import numpy as np
|
|
9
|
+
import pandas as pd
|
|
10
|
+
from rdkit import Chem
|
|
11
|
+
from rdkit.Chem import Draw
|
|
12
|
+
from rdkit.Chem.AllChem import ETKDGv3, EmbedMolecule, MMFFOptimizeMolecule, MMFFGetMoleculeProperties, MMFFGetMoleculeForceField
|
|
13
|
+
import os
|
|
14
|
+
import re
|
|
15
|
+
import glob
|
|
16
|
+
import json
|
|
17
|
+
import copy
|
|
18
|
+
|
|
19
|
+
parser = argparse.ArgumentParser(prog='Dock',
|
|
20
|
+
description='Command line implementation of SMINA for docking')
|
|
21
|
+
|
|
22
|
+
parser.add_argument('-i', '--input', help='Absolute path to an input file')
|
|
23
|
+
parser.add_argument('-o', '--output', help='Absolute path to an output file')
|
|
24
|
+
parser.add_argument('-c', '--conf', help="Absolute path to .json configuration file")
|
|
25
|
+
parser.add_argument('-s', '--smiles_col', help="Name of the column holding SMILES.")
|
|
26
|
+
parser.add_argument('-n', '--mol_id', help='Name of the column with Mol_ID')
|
|
27
|
+
parser.add_argument('-d', '--dock_col', help='Name of the column where to save the results')
|
|
28
|
+
|
|
29
|
+
parsed_args = parser.parse_args()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def main(args):
|
|
33
|
+
""" File with classes and functions related to computational-chemistry related tasks """
|
|
34
|
+
|
|
35
|
+
class Molecule:
|
|
36
|
+
|
|
37
|
+
def __init__(self, smiles: str, mol_id: str):
|
|
38
|
+
|
|
39
|
+
self.smiles = smiles
|
|
40
|
+
self.mol_id = mol_id
|
|
41
|
+
self.mol = Chem.MolFromSmiles(self.smiles)
|
|
42
|
+
self.dock_score = None
|
|
43
|
+
|
|
44
|
+
def embed(self, attempts: int = 10):
|
|
45
|
+
"""
|
|
46
|
+
Attempt to find lowest-energy 3D coordinates from 2D molecule.
|
|
47
|
+
"""
|
|
48
|
+
self.mol = Chem.AddHs(self.mol)
|
|
49
|
+
params = ETKDGv3()
|
|
50
|
+
mols = []
|
|
51
|
+
energies = []
|
|
52
|
+
|
|
53
|
+
def optimize_molecule(mol_, conf_id_):
|
|
54
|
+
ex_code = MMFFOptimizeMolecule(mol_, maxIters=500, confId=conf_id_)
|
|
55
|
+
match ex_code:
|
|
56
|
+
case -1: # optimization not possible, return whatever molecule was embedded
|
|
57
|
+
print('Could not setup the force field for provided molecule')
|
|
58
|
+
return mol_, np.nan
|
|
59
|
+
case 0: # optimization converged, proceed normally
|
|
60
|
+
prop = MMFFGetMoleculeProperties(mol_)
|
|
61
|
+
ff = MMFFGetMoleculeForceField(mol_, prop)
|
|
62
|
+
energy_ = ff.CalcEnergy()
|
|
63
|
+
return mol_, energy_
|
|
64
|
+
case 1: # force field setup correct, but convergence not reached
|
|
65
|
+
_ = MMFFOptimizeMolecule(mol_, maxIters=5000, confId=conf_id_)
|
|
66
|
+
prop = MMFFGetMoleculeProperties(mol_)
|
|
67
|
+
ff = MMFFGetMoleculeForceField(mol_, prop)
|
|
68
|
+
energy_ = ff.CalcEnergy()
|
|
69
|
+
return mol_, energy_
|
|
70
|
+
|
|
71
|
+
for attempt in range(attempts):
|
|
72
|
+
mol = copy.deepcopy(self.mol)
|
|
73
|
+
conf_id = EmbedMolecule(mol, params)
|
|
74
|
+
if conf_id >= 0: # in case of correct embedding the returned number is not negative
|
|
75
|
+
mol, energy = optimize_molecule(mol, conf_id)
|
|
76
|
+
mols.append(mol)
|
|
77
|
+
energies.append(energy)
|
|
78
|
+
else:
|
|
79
|
+
conf_id = EmbedMolecule(mol, maxAttempts=1000, useRandomCoords=True)
|
|
80
|
+
if isinstance(conf_id, int): # check if other embedding works
|
|
81
|
+
mol, energy = optimize_molecule(mol, conf_id)
|
|
82
|
+
mols.append(mol)
|
|
83
|
+
energies.append(energy)
|
|
84
|
+
else:
|
|
85
|
+
return mol # return unoptimized mol
|
|
86
|
+
|
|
87
|
+
if not all([x is np.nan for x in energies]): # if there is at least one correct energy
|
|
88
|
+
min_id = np.nanargmin(energies)
|
|
89
|
+
return mols[min_id] # return mol with the lowest energy
|
|
90
|
+
else:
|
|
91
|
+
print(f'Optimization not possible for < {self.smiles} >')
|
|
92
|
+
mol = copy.deepcopy(self.mol)
|
|
93
|
+
EmbedMolecule(mol)
|
|
94
|
+
return mol # return original molecule
|
|
95
|
+
|
|
96
|
+
def show(self):
|
|
97
|
+
return Draw.MolToImage(self.mol, size=(512, 512))
|
|
98
|
+
|
|
99
|
+
def dock(self, dock_params: dict):
|
|
100
|
+
|
|
101
|
+
output = dock_params['output_dir'].rstrip('/')
|
|
102
|
+
|
|
103
|
+
os.makedirs(output, exist_ok=True) # make dir if it's not there yet
|
|
104
|
+
|
|
105
|
+
mol_in = f'{output}/mol_{self.mol_id}.mol'
|
|
106
|
+
mol_out = f'{output}/mol_{self.mol_id}.mol2'
|
|
107
|
+
|
|
108
|
+
if glob.glob(mol_in):
|
|
109
|
+
print(f'Molecule file mol_{self.mol_id}.mol already exists.')
|
|
110
|
+
else:
|
|
111
|
+
self.mol = self.embed()
|
|
112
|
+
Chem.MolToMolFile(self.mol, mol_in)
|
|
113
|
+
convert_command = f'obabel -imol {mol_in} -omol2 -O {mol_out}'
|
|
114
|
+
os.system(convert_command)
|
|
115
|
+
|
|
116
|
+
smina = dock_params['smina_path']
|
|
117
|
+
protein = dock_params['protein_path']
|
|
118
|
+
mol2_in = f'{output}/mol_{self.mol_id}.mol2'
|
|
119
|
+
mol2_out = f'{output}/mol_{self.mol_id}_out.mol2'
|
|
120
|
+
log_out = f'{output}/mol_{self.mol_id}_out.log'
|
|
121
|
+
|
|
122
|
+
x_coord = dock_params['x_coord']
|
|
123
|
+
x_size = dock_params['x_size']
|
|
124
|
+
|
|
125
|
+
y_coord = dock_params['y_coord']
|
|
126
|
+
y_size = dock_params['y_size']
|
|
127
|
+
|
|
128
|
+
z_coord = dock_params['z_coord']
|
|
129
|
+
z_size = dock_params['z_size']
|
|
130
|
+
|
|
131
|
+
exh = dock_params['exhaustiveness']
|
|
132
|
+
|
|
133
|
+
dock_command = (f'{smina} -r {protein} -l {mol2_in} --center_x {x_coord} --center_y {y_coord} --center_z {z_coord} '
|
|
134
|
+
f'--size_x {x_size} --size_y {y_size} --size_z {z_size} --exhaustiveness {exh} --out {mol2_out};')
|
|
135
|
+
|
|
136
|
+
os.system(dock_command)
|
|
137
|
+
|
|
138
|
+
output_command = f'{smina} -r {protein} -l {mol2_out} --score_only --log {log_out}'
|
|
139
|
+
|
|
140
|
+
os.system(output_command)
|
|
141
|
+
|
|
142
|
+
output = open(log_out, 'r').read()
|
|
143
|
+
score = np.min([float(score) for score in re.findall(r'Affinity:\s*([\-?+\d.]+)\s+\(kcal/mol\)', output)])
|
|
144
|
+
|
|
145
|
+
self.dock_score = score
|
|
146
|
+
|
|
147
|
+
def read_dock(self, output_dir: str):
|
|
148
|
+
|
|
149
|
+
log_file = glob.glob(f"{output_dir.rstrip('/')}/mol_{self.mol_id}_out.log")
|
|
150
|
+
|
|
151
|
+
if len(log_file) == 0:
|
|
152
|
+
print('Molecule not yet docked')
|
|
153
|
+
self.dock_score = np.nan
|
|
154
|
+
else:
|
|
155
|
+
output = open(log_file[0], 'r').read()
|
|
156
|
+
self.dock_score = np.min([float(score) for score in re.findall(r'Affinity:\s*([\-?+\d.]+)\s+\(kcal/mol\)', output)])
|
|
157
|
+
|
|
158
|
+
def read_df(path):
|
|
159
|
+
ext = path.split('.')[-1]
|
|
160
|
+
if ext == 'xlsx':
|
|
161
|
+
df = pd.read_excel(path)
|
|
162
|
+
elif ext == 'csv':
|
|
163
|
+
df = pd.read_csv(path)
|
|
164
|
+
elif ext == 'parquet':
|
|
165
|
+
df = pd.read_parquet(path)
|
|
166
|
+
elif ext == 'pkl':
|
|
167
|
+
df = pd.read_pickle(path)
|
|
168
|
+
else:
|
|
169
|
+
raise ValueError('Incorrect extension')
|
|
170
|
+
|
|
171
|
+
return df
|
|
172
|
+
|
|
173
|
+
def write_df(df, path):
|
|
174
|
+
ext = path.split('.')[-1]
|
|
175
|
+
if len(df) >= 1:
|
|
176
|
+
if ext == 'xlsx':
|
|
177
|
+
df.to_excel(path, index=False)
|
|
178
|
+
elif ext == 'csv':
|
|
179
|
+
df.to_csv(path, index=False, header=True)
|
|
180
|
+
elif ext == 'parquet':
|
|
181
|
+
df.to_parquet(path)
|
|
182
|
+
elif ext == 'pkl':
|
|
183
|
+
df.to_pickle(path)
|
|
184
|
+
else:
|
|
185
|
+
raise ValueError('Incorrect extension')
|
|
186
|
+
|
|
187
|
+
def dock_score(row, smiles_col_, mol_id_col_, dock_params_):
|
|
188
|
+
smiles = row[smiles_col_]
|
|
189
|
+
mol_id = row[mol_id_col_]
|
|
190
|
+
mol = Molecule(smiles, mol_id)
|
|
191
|
+
mol.dock(dock_params_)
|
|
192
|
+
score = mol.dock_score
|
|
193
|
+
return score
|
|
194
|
+
|
|
195
|
+
input_path = args.input
|
|
196
|
+
output_path = args.output
|
|
197
|
+
config_path = args.conf
|
|
198
|
+
smiles_col = args.smiles_col
|
|
199
|
+
mol_id_col = args.mol_id
|
|
200
|
+
dock_col = args.dock_col
|
|
201
|
+
|
|
202
|
+
data = read_df(input_path)
|
|
203
|
+
|
|
204
|
+
dock_params = json.load(open(config_path, 'r'))
|
|
205
|
+
|
|
206
|
+
if mol_id_col not in data.columns:
|
|
207
|
+
data[mol_id_col] = np.arange(0, len(data))
|
|
208
|
+
|
|
209
|
+
data[dock_col] = data.apply(dock_score, smiles_col_=smiles_col, mol_id_col_=mol_id_col, dock_params_=dock_params, axis=1)
|
|
210
|
+
|
|
211
|
+
write_df(data, output_path)
|
|
212
|
+
|
|
213
|
+
|
|
214
|
+
if __name__ == '__main__':
|
|
215
|
+
main(parsed_args)
|