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.
Files changed (68) hide show
  1. vantami/__init__.py +12 -0
  2. vantami/api/__init__.py +0 -0
  3. vantami/api/convert.py +45 -0
  4. vantami/api/resolve.py +290 -0
  5. vantami/cache.py +14 -0
  6. vantami/chemistry/__init__.py +0 -0
  7. vantami/chemistry/molecule.py +273 -0
  8. vantami/cli/CDDD.py +63 -0
  9. vantami/cli/CIR.py +185 -0
  10. vantami/cli/Dock.py +215 -0
  11. vantami/cli/MolStandardizer.py +635 -0
  12. vantami/cli/Mordred.py +76 -0
  13. vantami/cli/OptunaOptimization.py +151 -0
  14. vantami/cli/__init__.py +0 -0
  15. vantami/data/__init__.py +0 -0
  16. vantami/data/cluster.py +230 -0
  17. vantami/data/descriptors.py +1076 -0
  18. vantami/data/distance.py +539 -0
  19. vantami/data/filter.py +69 -0
  20. vantami/data/manager.py +309 -0
  21. vantami/data/manipulate.py +94 -0
  22. vantami/data/partition.py +659 -0
  23. vantami/data/transform.py +606 -0
  24. vantami/deep/__init__.py +5 -0
  25. vantami/deep/dataset.py +252 -0
  26. vantami/deep/loader.py +172 -0
  27. vantami/deep/models.py +625 -0
  28. vantami/deep/modules.py +704 -0
  29. vantami/deep/utils.py +28 -0
  30. vantami/deep/vectorizer.py +389 -0
  31. vantami/io/__init__.py +2 -0
  32. vantami/io/database.py +303 -0
  33. vantami/io/file.py +99 -0
  34. vantami/metrics/__init__.py +0 -0
  35. vantami/metrics/modellability.py +30 -0
  36. vantami/ml/__init__.py +0 -0
  37. vantami/ml/augood.py +227 -0
  38. vantami/ml/evaluate.py +151 -0
  39. vantami/ml/models.py +617 -0
  40. vantami/ml/optimize.py +319 -0
  41. vantami/ml/params.py +331 -0
  42. vantami/ml/score.py +170 -0
  43. vantami/ml/select.py +150 -0
  44. vantami/ml/utils.py +125 -0
  45. vantami/nlp/__init__.py +0 -0
  46. vantami/nlp/article.py +197 -0
  47. vantami/nlp/cluster.py +32 -0
  48. vantami/nlp/tokenize.py +60 -0
  49. vantami/standardize/__init__.py +0 -0
  50. vantami/standardize/clean.py +853 -0
  51. vantami/standardize/duplicates.py +338 -0
  52. vantami/standardize/filter.py +699 -0
  53. vantami/standardize/validate.py +105 -0
  54. vantami/stats/__init__.py +0 -0
  55. vantami/stats/tests.py +111 -0
  56. vantami/stats/utils.py +15 -0
  57. vantami/visualize/__init__.py +0 -0
  58. vantami/visualize/ecdf.py +79 -0
  59. vantami/visualize/embedding.py +93 -0
  60. vantami/visualize/performance.py +26 -0
  61. vantami/visualize/predictions.py +352 -0
  62. vantami/visualize/properties.py +322 -0
  63. vantami/visualize/utils.py +241 -0
  64. vantami-0.4.3.dist-info/METADATA +143 -0
  65. vantami-0.4.3.dist-info/RECORD +68 -0
  66. vantami-0.4.3.dist-info/WHEEL +5 -0
  67. vantami-0.4.3.dist-info/licenses/LICENSE +28 -0
  68. 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)