rdworks 0.25.8__py3-none-any.whl → 0.36.1__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.
- rdworks/__init__.py +19 -20
- rdworks/conf.py +319 -118
- rdworks/display.py +244 -83
- rdworks/mol.py +620 -489
- rdworks/mollibr.py +336 -180
- rdworks/readin.py +2 -4
- rdworks/scaffold.py +1 -1
- rdworks/std.py +64 -24
- rdworks/torsion.py +477 -0
- rdworks/units.py +7 -58
- rdworks/utils.py +141 -258
- rdworks/xtb/__init__.py +0 -0
- rdworks/xtb/wrapper.py +304 -0
- {rdworks-0.25.8.dist-info → rdworks-0.36.1.dist-info}/METADATA +6 -9
- {rdworks-0.25.8.dist-info → rdworks-0.36.1.dist-info}/RECORD +18 -15
- {rdworks-0.25.8.dist-info → rdworks-0.36.1.dist-info}/WHEEL +1 -1
- {rdworks-0.25.8.dist-info → rdworks-0.36.1.dist-info}/licenses/LICENSE +0 -0
- {rdworks-0.25.8.dist-info → rdworks-0.36.1.dist-info}/top_level.txt +0 -0
rdworks/__init__.py
CHANGED
@@ -1,35 +1,34 @@
|
|
1
|
-
__version__ = '0.
|
1
|
+
__version__ = '0.36.1'
|
2
|
+
|
3
|
+
from rdworks.conf import Conf
|
4
|
+
from rdworks.mol import Mol
|
5
|
+
from rdworks.mollibr import MolLibr
|
2
6
|
|
3
|
-
from rdworks.xml import list_predefined_xml, get_predefined_xml, parse_xml
|
4
|
-
from rdworks.units import ev2kcalpermol, hartree2ev, hartree2kcalpermol, periodictable
|
5
|
-
from rdworks.readin import read_csv, merge_csv, read_dataframe, read_smi, read_sdf, read_mae
|
6
|
-
from rdworks.std import desalt_smiles, standardize_smiles, standardize
|
7
|
-
from rdworks.tautomers import complete_tautomers
|
8
7
|
from rdworks.stereoisomers import complete_stereoisomers
|
8
|
+
from rdworks.tautomers import complete_tautomers
|
9
9
|
from rdworks.ionized import IonizedStates
|
10
|
+
|
11
|
+
from rdworks.readin import read_csv, merge_csv, read_dataframe, read_smi, read_sdf, read_mae
|
12
|
+
from rdworks.std import desalt_smiles, standardize_smiles, standardize
|
13
|
+
|
10
14
|
from rdworks.rgroup import expand_rgroup, most_common, most_common_in_NP
|
11
15
|
from rdworks.scaffold import scaffold_network, scaffold_tree, BRICS_fragmented, BRICS_fragment_indices
|
12
16
|
from rdworks.matchedseries import MatchedSeries
|
13
|
-
from rdworks.descriptor import rd_descriptor, rd_descriptor_f
|
14
|
-
from rdworks.utils import fix_decimal_places_in_list, fix_decimal_places_in_dict, mae_to_dict, mae_rd_index
|
15
|
-
from rdworks.display import svg
|
16
|
-
from rdworks.conf import Conf
|
17
|
-
from rdworks.mol import Mol
|
18
|
-
from rdworks.mollibr import MolLibr
|
19
17
|
|
20
|
-
from
|
21
|
-
|
18
|
+
from rdworks.descriptor import rd_descriptor, rd_descriptor_f
|
19
|
+
from rdworks.xml import list_predefined_xml, get_predefined_xml, parse_xml
|
22
20
|
|
21
|
+
import rdkit
|
23
22
|
import logging
|
24
23
|
|
24
|
+
__rdkit_version__ = rdkit.rdBase.rdkitVersion
|
25
|
+
|
26
|
+
rdkit_logger = rdkit.RDLogger.logger().setLevel(rdkit.RDLogger.CRITICAL)
|
27
|
+
|
25
28
|
main_logger = logging.getLogger()
|
26
29
|
main_logger.setLevel(logging.INFO) # level: DEBUG < INFO < WARNING < ERROR < CRITICAL
|
27
|
-
logger_formatter = logging.Formatter(
|
28
|
-
|
29
|
-
datefmt='%Y-%m-%d %H:%M:%S')
|
30
|
+
logger_formatter = logging.Formatter(fmt='%(asctime)s %(levelname)s %(message)s',
|
31
|
+
datefmt='%Y-%m-%d %H:%M:%S')
|
30
32
|
logger_ch = logging.StreamHandler()
|
31
33
|
logger_ch.setFormatter(logger_formatter)
|
32
34
|
main_logger.addHandler(logger_ch)
|
33
|
-
|
34
|
-
|
35
|
-
__rdkit_version__ = rdBase.rdkitVersion
|
rdworks/conf.py
CHANGED
@@ -2,45 +2,71 @@ import io
|
|
2
2
|
import copy
|
3
3
|
import json
|
4
4
|
import numpy as np
|
5
|
-
|
6
5
|
import ase
|
7
|
-
from ase.optimize import FIRE
|
8
6
|
|
9
7
|
from collections.abc import Callable
|
8
|
+
from typing import Self
|
9
|
+
|
10
|
+
from mendeleev import element
|
11
|
+
from ase.optimize import FIRE
|
10
12
|
|
11
13
|
from rdkit import Chem
|
12
|
-
from rdkit.Chem import rdMolTransforms, AllChem, rdMolAlign
|
14
|
+
from rdkit.Chem import rdMolTransforms, AllChem, rdMolAlign, rdmolops
|
13
15
|
from rdkit.Chem.Draw import rdMolDraw2D
|
16
|
+
from PIL import Image
|
14
17
|
|
15
|
-
from
|
16
|
-
|
17
|
-
from rdworks.
|
18
|
-
from rdworks.
|
18
|
+
from rdworks.units import ev2kcalpermol, pm2angstrom
|
19
|
+
from rdworks.utils import recursive_round
|
20
|
+
from rdworks.xtb.wrapper import GFN2xTB
|
21
|
+
from rdworks.display import render_png, render_svg
|
19
22
|
|
20
23
|
|
21
24
|
class Conf:
|
22
|
-
"""Container for 3D conformers.
|
23
|
-
"""
|
25
|
+
"""Container for 3D conformers."""
|
24
26
|
|
25
|
-
def __init__(self,
|
26
|
-
"""
|
27
|
+
def __init__(self, molecule: str | Chem.Mol | None = None, name: str='') -> None:
|
28
|
+
"""Initialize.
|
27
29
|
|
28
30
|
Args:
|
29
|
-
|
30
|
-
name (str
|
31
|
+
molecule (Chem.Mol | MolBlock string): Molecule for 3D conformer.
|
32
|
+
name (str): Name prefix of the generated conformers. Defaults to ''.
|
31
33
|
|
32
34
|
Raises:
|
33
|
-
ValueError: if `
|
35
|
+
ValueError: if `molecule` is not rdkit.Chem.Mol object.
|
34
36
|
"""
|
35
|
-
|
37
|
+
assert isinstance(molecule, str | Chem.Mol) or molecule is None
|
38
|
+
|
39
|
+
self.rdmol = None # must contain one and only one rdkit conformer
|
36
40
|
self.name = name
|
41
|
+
self.natoms = 0
|
42
|
+
self.charge = 0 # molecular charge
|
43
|
+
self.spin = 1 # molecular spin multiplicity for ASE
|
37
44
|
self.props = {}
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
45
|
+
|
46
|
+
if molecule is None:
|
47
|
+
return
|
48
|
+
|
49
|
+
if isinstance(molecule, str): # 3-D MolBLock string
|
50
|
+
try:
|
51
|
+
self.rdmol = Chem.MolFromMolBlock(molecule)
|
52
|
+
except:
|
53
|
+
ValueError(f'Conf() Error: invalid MolBlock string')
|
54
|
+
|
55
|
+
elif isinstance(molecule, Chem.Mol): # 3-D
|
56
|
+
try:
|
57
|
+
self.rdmol = molecule
|
58
|
+
except:
|
59
|
+
ValueError(f'Conf() Error: invalid Chem.Mol object')
|
60
|
+
|
61
|
+
num_atoms = self.rdmol.GetNumAtoms()
|
62
|
+
tot_atoms = self.rdmol.GetNumAtoms(onlyExplicit=False)
|
63
|
+
assert num_atoms == tot_atoms, "Conf() Error: missing hydrogens"
|
64
|
+
self.natoms = num_atoms
|
65
|
+
self.charge = rdmolops.GetFormalCharge(self.rdmol)
|
66
|
+
self.props.update({'atoms': num_atoms})
|
67
|
+
|
68
|
+
assert self.rdmol.GetConformer().Is3D(), "Conf() Error: not 3D"
|
69
|
+
|
44
70
|
|
45
71
|
|
46
72
|
def __str__(self) -> str:
|
@@ -50,12 +76,7 @@ class Conf:
|
|
50
76
|
str: string representation.
|
51
77
|
"""
|
52
78
|
return f"<rdworks.Conf({self.rdmol} name={self.name} atoms={self.natoms})>"
|
53
|
-
|
54
|
-
|
55
|
-
##################################################
|
56
|
-
### Cascading methods
|
57
|
-
##################################################
|
58
|
-
|
79
|
+
|
59
80
|
|
60
81
|
def copy(self) -> Self:
|
61
82
|
"""Returns a copy of self.
|
@@ -85,7 +106,7 @@ class Conf:
|
|
85
106
|
return self
|
86
107
|
|
87
108
|
|
88
|
-
def sync(self, coord:
|
109
|
+
def sync(self, coord: np.ndarray | list) -> Self:
|
89
110
|
"""Synchronize the conformer coordinates with the provided `coord`.
|
90
111
|
|
91
112
|
Args:
|
@@ -107,56 +128,31 @@ class Conf:
|
|
107
128
|
return self
|
108
129
|
|
109
130
|
|
110
|
-
def
|
111
|
-
"""
|
131
|
+
def set_torsion(self, i:int, j:int, k:int, l:int, degree:float) -> Self:
|
132
|
+
"""Set dihedral angle (i-j-k-l) in degrees.
|
112
133
|
|
113
134
|
Args:
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
bending and dihedral torsions to favor planar geometries for specific nitrogen atoms.
|
120
|
-
This makes it better suited for geometry optimization studies where a static,
|
121
|
-
time-averaged structure is desired. The "s" stands for "static".
|
122
|
-
`UFF` - UFF refers to the "Universal Force Field," a force field model used for
|
123
|
-
molecular mechanics calculations. It's a tool for geometry optimization,
|
124
|
-
energy minimization, and exploring molecular conformations in 3D space.
|
125
|
-
UFF is often used to refine conformers generated by other methods,
|
126
|
-
such as random conformer generation, to produce more physically plausible
|
127
|
-
and stable structures.
|
135
|
+
i (int): atom index
|
136
|
+
j (int): atom index
|
137
|
+
k (int): atom index
|
138
|
+
l (int): atom index
|
139
|
+
degree (float): dihedral angle in degrees
|
128
140
|
|
129
141
|
Returns:
|
130
|
-
|
142
|
+
Self: modified Conf object
|
131
143
|
"""
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
mp = AllChem.MMFFGetMoleculeProperties(self.rdmol, mmffVariant='MMFF94')
|
136
|
-
ff = AllChem.MMFFGetMoleculeForceField(self.rdmol, mp)
|
137
|
-
elif calculator == 'MMFF94s':
|
138
|
-
mp = AllChem.MMFFGetMoleculeProperties(self.rdmol, mmffVariant='MMFF94s')
|
139
|
-
ff = AllChem.MMFFGetMoleculeForceField(self.rdmol, mp)
|
140
|
-
elif calculator == 'UFF':
|
141
|
-
ff = AllChem.UFFGetMoleculeForceField(self.rdmol)
|
142
|
-
else:
|
143
|
-
raise ValueError("Unsupported calculator")
|
144
|
-
PE = ff.CalcEnergy()
|
145
|
-
self.props.update({'E_tot(kcal/mol)': PE})
|
146
|
-
else:
|
147
|
-
try:
|
148
|
-
ase_atoms = ase.Atoms(symbols=self.symbols(), positions=self.positions())
|
149
|
-
ase_atoms.calc = calculator
|
150
|
-
PE = ase_atoms.get_potential_energy() # np.array
|
151
|
-
PE = ev2kcalpermol * float(PE[0]) # np.float64 to float
|
152
|
-
self.props.update({'E_tot(kcal/mol)': PE})
|
153
|
-
except:
|
154
|
-
raise RuntimeError("ASE calculator error")
|
155
|
-
return PE
|
144
|
+
rdMolTransforms.SetDihedralDeg(self.rdmol.GetConformer(), i, j, k, l, degree)
|
145
|
+
|
146
|
+
return self
|
156
147
|
|
157
148
|
|
158
|
-
|
159
|
-
|
149
|
+
|
150
|
+
def optimize(self,
|
151
|
+
calculator: str | Callable = 'MMFF94',
|
152
|
+
fmax:float=0.05,
|
153
|
+
max_iter:int=1000,
|
154
|
+
**kwargs) -> Self:
|
155
|
+
"""Optimize 3D geometry.
|
160
156
|
|
161
157
|
Args:
|
162
158
|
calculator (str | Callable): MMFF94 (= MMFF), MMFF94s, UFF, or ASE calculator.
|
@@ -173,33 +169,56 @@ class Conf:
|
|
173
169
|
UFF is often used to refine conformers generated by other methods,
|
174
170
|
such as random conformer generation, to produce more physically plausible
|
175
171
|
and stable structures.
|
176
|
-
fmax (float, optional): fmax for the calculator. Defaults to 0.05.
|
172
|
+
fmax (float, optional): fmax for the calculator convergence. Defaults to 0.05.
|
173
|
+
max_iter (int, optional): max iterations for the calculator. Defaults to 1000.
|
174
|
+
|
175
|
+
Args for xTB:
|
176
|
+
water (str, optional): water solvation model (choose 'gbsa' or 'alpb')
|
177
|
+
alpb: ALPB solvation model (Analytical Linearized Poisson-Boltzmann).
|
178
|
+
gbsa: generalized Born (GB) model with Surface Area contributions.
|
177
179
|
|
178
180
|
Returns:
|
179
181
|
Self: self
|
180
182
|
"""
|
181
183
|
if isinstance(calculator, str) :
|
182
|
-
|
183
|
-
|
184
|
-
|
184
|
+
|
185
|
+
PE_start = self.potential_energy(calculator)
|
186
|
+
|
187
|
+
if calculator.lower() == 'xTB'.lower():
|
188
|
+
water = kwargs.get('water', None)
|
189
|
+
PE_final, self.rdmol = GFN2xTB(self.rdmol).optimize(water=water)
|
190
|
+
|
191
|
+
elif calculator.lower() == 'MMFF94'.lower() or calculator.lower() == 'MMFF'.lower():
|
192
|
+
retcode = Chem.rdForceFieldHelpers.MMFFOptimizeMolecule(self.rdmol,
|
193
|
+
mmffVariant='MMFF94',
|
194
|
+
maxIters=max_iter)
|
185
195
|
# returns 0 if the optimization converged
|
186
|
-
elif calculator == 'MMFF94s':
|
187
|
-
retcode =
|
196
|
+
elif calculator.lower() == 'MMFF94s'.lower():
|
197
|
+
retcode = Chem.rdForceFieldHelpers.MMFFOptimizeMolecule(self.rdmol,
|
198
|
+
mmffVariant='MMFF94s',
|
199
|
+
maxIters=max_iter)
|
188
200
|
# returns 0 if the optimization converged
|
189
|
-
elif calculator == 'UFF':
|
190
|
-
retcode =
|
201
|
+
elif calculator.lower() == 'UFF'.lower():
|
202
|
+
retcode = Chem.rdForceFieldHelpers.UFFOptimizeMolecule(self.rdmol,
|
203
|
+
maxIters=max_iter)
|
191
204
|
# returns 0 if the optimization converged
|
192
|
-
|
205
|
+
|
206
|
+
PE_final = self.potential_energy(calculator)
|
207
|
+
|
193
208
|
self.props.update({
|
194
|
-
'E_tot_init(kcal/mol)':
|
195
|
-
'E_tot(kcal/mol)':
|
209
|
+
'E_tot_init(kcal/mol)': PE_start , # energy before optimization
|
210
|
+
'E_tot(kcal/mol)': PE_final, # energy after optimization
|
196
211
|
'Converged' : retcode == 0, # True or False
|
197
212
|
})
|
213
|
+
|
198
214
|
return self
|
199
215
|
|
200
216
|
else:
|
217
|
+
# assuming ASE calculator
|
201
218
|
with io.StringIO() as logfile:
|
202
219
|
ase_atoms = ase.Atoms(symbols=self.symbols(), positions=self.positions())
|
220
|
+
ase_atoms.info['charge'] = self.charge
|
221
|
+
ase_atoms.info['spin'] = self.spin
|
203
222
|
ase_atoms.calc = calculator
|
204
223
|
FIRE(ase_atoms, logfile=logfile).run(fmax=fmax)
|
205
224
|
lines = [l.strip().split()[1:] for l in logfile.getvalue().split('\n') if l.startswith('FIRE')]
|
@@ -209,9 +228,11 @@ class Conf:
|
|
209
228
|
'E_tot(kcal/mol)': data[-1][0] * ev2kcalpermol, # energy after optimization
|
210
229
|
'Converged' : data[-1][1] < fmax, # True or False
|
211
230
|
})
|
231
|
+
|
212
232
|
# update atomic coordinates
|
213
233
|
return self.sync(ase_atoms.get_positions())
|
214
234
|
|
235
|
+
|
215
236
|
|
216
237
|
##################################################
|
217
238
|
### Endpoint methods
|
@@ -233,13 +254,13 @@ class Conf:
|
|
233
254
|
idx2 = bond.GetEndAtomIdx()
|
234
255
|
nuc1 = self.rdmol.GetAtomWithIdx(idx1).GetAtomicNum()
|
235
256
|
nuc2 = self.rdmol.GetAtomWithIdx(idx2).GetAtomicNum()
|
236
|
-
sum_radii = (
|
257
|
+
sum_radii = (element(nuc1).vdw_radius + element(nuc2).vdw_radius) * pm2angstrom
|
237
258
|
bond_length = rdMolTransforms.GetBondLength(self.rdmol.GetConformer(), idx1, idx2)
|
238
259
|
if abs(bond_length - sum_radii) > tolerance:
|
239
260
|
return False
|
240
261
|
|
241
262
|
return True
|
242
|
-
|
263
|
+
|
243
264
|
|
244
265
|
def positions(self) -> np.array:
|
245
266
|
"""Returns the coordinates.
|
@@ -298,14 +319,99 @@ class Conf:
|
|
298
319
|
return np.sqrt(np.mean(b))
|
299
320
|
|
300
321
|
|
301
|
-
def
|
322
|
+
def potential_energy(self, calculator: str | Callable = 'MMFF94', **kwargs) -> float:
|
323
|
+
"""Get potential energy and set `E_tot(kcal/mol)` in the self.props.
|
324
|
+
|
325
|
+
Args:
|
326
|
+
calculator (str | Callable): MMFF94 (= MMFF), MMFF94s, UFF, or ASE calculator.
|
327
|
+
`MMFF94` or `MMFF` - Intended for general use, including organic molecules and proteins,
|
328
|
+
and primarily relies on data from quantum mechanical calculations.
|
329
|
+
It's often used in molecular dynamics simulations.
|
330
|
+
`MMFF94s` - A "static" variant of MMFF94, with adjusted parameters for out-of-plane
|
331
|
+
bending and dihedral torsions to favor planar geometries for specific nitrogen atoms.
|
332
|
+
This makes it better suited for geometry optimization studies where a static,
|
333
|
+
time-averaged structure is desired. The "s" stands for "static".
|
334
|
+
`UFF` - UFF refers to the "Universal Force Field," a force field model used for
|
335
|
+
molecular mechanics calculations. It's a tool for geometry optimization,
|
336
|
+
energy minimization, and exploring molecular conformations in 3D space.
|
337
|
+
UFF is often used to refine conformers generated by other methods,
|
338
|
+
such as random conformer generation, to produce more physically plausible
|
339
|
+
and stable structures.
|
340
|
+
|
341
|
+
Returns:
|
342
|
+
float | None: potential energy in kcal/mol or None.
|
343
|
+
"""
|
344
|
+
|
345
|
+
if isinstance(calculator, str):
|
346
|
+
if calculator.lower() == 'xTB'.lower():
|
347
|
+
water = kwargs.get('water', None)
|
348
|
+
PE = GFN2xTB(self.rdmol).singlepoint(water=water)
|
349
|
+
|
350
|
+
elif calculator.lower() == 'MMFF94'.lower() or calculator.lower() == 'MMFF'.lower():
|
351
|
+
mp = Chem.rdForceFieldHelpers.MMFFGetMoleculeProperties(self.rdmol, mmffVariant='MMFF94')
|
352
|
+
ff = Chem.rdForceFieldHelpers.MMFFGetMoleculeForceField(self.rdmol, mp)
|
353
|
+
PE = ff.CalcEnergy()
|
354
|
+
|
355
|
+
elif calculator.lower() == 'MMFF94s'.lower():
|
356
|
+
mp = Chem.rdForceFieldHelpers.MMFFGetMoleculeProperties(self.rdmol, mmffVariant='MMFF94s')
|
357
|
+
ff = Chem.rdForceFieldHelpers.MMFFGetMoleculeForceField(self.rdmol, mp)
|
358
|
+
PE = ff.CalcEnergy()
|
359
|
+
|
360
|
+
elif calculator.lower() == 'UFF'.lower():
|
361
|
+
ff = Chem.rdForceFieldHelpers.UFFGetMoleculeForceField(self.rdmol)
|
362
|
+
PE = ff.CalcEnergy()
|
363
|
+
|
364
|
+
else:
|
365
|
+
raise ValueError("Unsupported calculator")
|
366
|
+
|
367
|
+
self.props.update({'E_tot(kcal/mol)': PE})
|
368
|
+
|
369
|
+
return PE
|
370
|
+
|
371
|
+
else:
|
372
|
+
try:
|
373
|
+
ase_atoms = ase.Atoms(symbols=self.symbols(), positions=self.positions())
|
374
|
+
ase_atoms.info['charge'] = self.charge
|
375
|
+
ase_atoms.info['spin'] = self.spin
|
376
|
+
ase_atoms.calc = calculator
|
377
|
+
PE = ase_atoms.get_potential_energy() # np.array
|
378
|
+
if isinstance(PE, float):
|
379
|
+
PE = ev2kcalpermol * PE
|
380
|
+
elif isinstance(PE, np.ndarray | list):
|
381
|
+
PE = ev2kcalpermol * float(PE[0]) # np.float64 to float
|
382
|
+
self.props.update({'E_tot(kcal/mol)': PE})
|
383
|
+
|
384
|
+
return PE
|
385
|
+
|
386
|
+
except:
|
387
|
+
raise RuntimeError("ASE calculator error")
|
388
|
+
|
389
|
+
|
390
|
+
def torsion_angle(self, i:int, j:int, k:int, l:int) -> float:
|
391
|
+
"""Get dihedral angle (i-j-k-l) in degrees.
|
392
|
+
|
393
|
+
Args:
|
394
|
+
i (int): atom index
|
395
|
+
j (int): atom index
|
396
|
+
k (int): atom index
|
397
|
+
l (int): atom index
|
398
|
+
|
399
|
+
Returns:
|
400
|
+
float: dihedral angle in degrees.
|
401
|
+
"""
|
402
|
+
degree = rdMolTransforms.GetDihedralDeg(self.rdmol.GetConformer(), i, j, k, l)
|
403
|
+
|
404
|
+
return degree
|
405
|
+
|
406
|
+
|
407
|
+
def dumps(self, key:str='') -> str:
|
302
408
|
"""Returns JSON dumps of the `props`.
|
303
409
|
|
304
410
|
Args:
|
305
411
|
key (str): a key for the `props` dictionary. Defaults to '' (all).
|
306
412
|
|
307
413
|
Returns:
|
308
|
-
|
414
|
+
str: JSON dumps.
|
309
415
|
"""
|
310
416
|
if key:
|
311
417
|
return json.dumps({key:self.props[key]})
|
@@ -313,6 +419,59 @@ class Conf:
|
|
313
419
|
return json.dumps(self.props)
|
314
420
|
|
315
421
|
|
422
|
+
def serialize(self, decimals:int=3) -> str:
|
423
|
+
"""Serialize information necessary to rebuild.
|
424
|
+
|
425
|
+
Returns:
|
426
|
+
str: serialized string for json.loads()
|
427
|
+
"""
|
428
|
+
serialized = json.dumps({
|
429
|
+
'name' : self.name,
|
430
|
+
'natoms': self.natoms,
|
431
|
+
'props' : recursive_round(self.props, decimals),
|
432
|
+
'molblock' : self.to_molblock(),
|
433
|
+
})
|
434
|
+
|
435
|
+
return serialized
|
436
|
+
|
437
|
+
|
438
|
+
def deserialize(self, serialized:str) -> Self:
|
439
|
+
"""De-serialize information and rebuild.
|
440
|
+
|
441
|
+
Args:
|
442
|
+
serialized (str): _description_
|
443
|
+
|
444
|
+
Returns:
|
445
|
+
Self: _description_
|
446
|
+
"""
|
447
|
+
data = json.loads(serialized)
|
448
|
+
|
449
|
+
self.name = data['name']
|
450
|
+
self.natoms = data['natoms']
|
451
|
+
self.props = data['props']
|
452
|
+
self.rdmol = Chem.MolFromMolBlock(data['molblock'], sanitize=False, removeHs=False)
|
453
|
+
|
454
|
+
return self
|
455
|
+
|
456
|
+
|
457
|
+
def to_molblock(self) -> str:
|
458
|
+
"""Returns MolBlock"""
|
459
|
+
return Chem.MolToMolBlock(self.rdmol)
|
460
|
+
|
461
|
+
|
462
|
+
def to_xyz(self) -> str:
|
463
|
+
"""Returns XYZ formatted strings.
|
464
|
+
|
465
|
+
Returns:
|
466
|
+
str: XYZ formatted strings.
|
467
|
+
"""
|
468
|
+
lines = [f'{self.natoms}', ' ']
|
469
|
+
for e, (x, y, z) in zip(self.symbols(), self.positions()):
|
470
|
+
lines.append(f'{e:5} {x:23.14f} {y:23.14f} {z:23.14f}')
|
471
|
+
|
472
|
+
return '\n'.join(lines)
|
473
|
+
|
474
|
+
|
316
475
|
def to_sdf(self, props:bool=True) -> str:
|
317
476
|
"""Returns the SDF-formatted strings.
|
318
477
|
|
@@ -332,43 +491,85 @@ class Conf:
|
|
332
491
|
f.write(rdmol)
|
333
492
|
return in_memory.getvalue()
|
334
493
|
|
494
|
+
|
495
|
+
def to_png(self,
|
496
|
+
width: int = 300,
|
497
|
+
height: int = 300,
|
498
|
+
legend: str = '',
|
499
|
+
atom_index: bool = False,
|
500
|
+
highlight_atoms: list[int] | None = None,
|
501
|
+
highlight_bonds: list[int] | None = None,
|
502
|
+
redraw: bool = False,
|
503
|
+
coordgen: bool = False,
|
504
|
+
trim: bool = True) -> Image.Image:
|
505
|
+
"""Draw 2D molecule in PNG format.
|
506
|
+
|
507
|
+
Args:
|
508
|
+
width (int, optional): width. Defaults to 300.
|
509
|
+
height (int, optional): height. Defaults to 300.
|
510
|
+
legend (str, optional): legend. Defaults to ''.
|
511
|
+
atom_index (bool, optional): whether to show atom index. Defaults to False.
|
512
|
+
highlight_atoms (list[int] | None, optional): atom(s) to highlight. Defaults to None.
|
513
|
+
highlight_bonds (list[int] | None, optional): bond(s) to highlight. Defaults to None.
|
514
|
+
redraw (bool, optional): whether to redraw. Defaults to False.
|
515
|
+
coordgen (bool, optional): whether to use coordgen. Defaults to False.
|
516
|
+
trim (bool, optional): whether to trim white margins. Default to True.
|
517
|
+
|
518
|
+
Returns:
|
519
|
+
Image.Image: output PIL Image object.
|
520
|
+
"""
|
521
|
+
|
522
|
+
return render_png(self.rdmol,
|
523
|
+
width = width,
|
524
|
+
height = height,
|
525
|
+
legend = legend,
|
526
|
+
atom_index = atom_index,
|
527
|
+
highlight_atoms = highlight_atoms,
|
528
|
+
highlight_bonds = highlight_bonds,
|
529
|
+
redraw = redraw,
|
530
|
+
coordgen = coordgen,
|
531
|
+
trim = trim)
|
532
|
+
|
335
533
|
|
336
|
-
def to_svg(self,
|
337
|
-
width:int=
|
338
|
-
height:int=
|
339
|
-
legend:
|
340
|
-
atom_index:bool=False,
|
341
|
-
|
342
|
-
|
534
|
+
def to_svg(self,
|
535
|
+
width: int = 300,
|
536
|
+
height: int = 300,
|
537
|
+
legend: str = '',
|
538
|
+
atom_index: bool = False,
|
539
|
+
highlight_atoms: list[int] | None = None,
|
540
|
+
highlight_bonds: list[int] | None = None,
|
541
|
+
redraw: bool = False,
|
542
|
+
coordgen: bool = False,
|
543
|
+
optimize: bool = True) -> str:
|
544
|
+
"""Draw 2D molecule in SVG format.
|
343
545
|
|
344
546
|
Examples:
|
547
|
+
For Jupyternotebook, wrap the output with SVG:
|
548
|
+
|
345
549
|
>>> from IPython.display import SVG
|
346
|
-
>>> SVG(libr[0].
|
550
|
+
>>> SVG(libr[0].to_svg())
|
347
551
|
|
348
552
|
Args:
|
349
|
-
width (int): width
|
350
|
-
height (int): height
|
351
|
-
legend (str, optional):
|
352
|
-
atom_index (bool):
|
353
|
-
|
354
|
-
|
553
|
+
width (int, optional): width. Defaults to 300.
|
554
|
+
height (int, optional): height. Defaults to 300.
|
555
|
+
legend (str, optional): legend. Defaults to ''.
|
556
|
+
atom_index (bool, optional): whether to show atom index. Defaults to False.
|
557
|
+
highlight_atoms (list[int] | None, optional): atom(s) to highlight. Defaults to None.
|
558
|
+
highlight_bonds (list[int] | None, optional): bond(s) to highlight. Defaults to None.
|
559
|
+
redraw (bool, optional): whether to redraw. Defaults to False.
|
560
|
+
coordgen (bool, optional): whether to use coordgen. Defaults to False.
|
561
|
+
optimize (bool, optional): whether to optimize SVG string. Defaults to True.
|
562
|
+
|
355
563
|
Returns:
|
356
|
-
str: SVG
|
564
|
+
str: SVG string
|
357
565
|
"""
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
atom.SetProp("atomLabel", str(atom.GetIdx()))
|
369
|
-
if highlight:
|
370
|
-
drawer.DrawMolecule(rdmol_3d, legend=legend, highlightAtoms=highlight)
|
371
|
-
else:
|
372
|
-
drawer.DrawMolecule(rdmol_3d, legend=legend)
|
373
|
-
drawer.FinishDrawing()
|
374
|
-
return drawer.GetDrawingText()
|
566
|
+
return render_svg(self.rdmol,
|
567
|
+
width = width,
|
568
|
+
height = height,
|
569
|
+
legend = legend,
|
570
|
+
atom_index = atom_index,
|
571
|
+
highlight_atoms = highlight_atoms,
|
572
|
+
highlight_bonds = highlight_bonds,
|
573
|
+
redraw = redraw,
|
574
|
+
coordgen = coordgen,
|
575
|
+
optimize = optimize)
|