chiralfinder 0.0.1__tar.gz
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.
- chiralfinder-0.0.1/LICENSE +21 -0
- chiralfinder-0.0.1/PKG-INFO +103 -0
- chiralfinder-0.0.1/README.md +74 -0
- chiralfinder-0.0.1/chiralfinder/__init__.py +22 -0
- chiralfinder-0.0.1/chiralfinder/_quadrupole/__init__.py +20 -0
- chiralfinder-0.0.1/chiralfinder/_quadrupole/quadrupole_center.py +48 -0
- chiralfinder-0.0.1/chiralfinder/_quadrupole/quadrupole_utils.py +142 -0
- chiralfinder-0.0.1/chiralfinder/_quadrupole/quadrupole_v1.py +72 -0
- chiralfinder-0.0.1/chiralfinder/_quadrupole/quadrupole_v2.py +273 -0
- chiralfinder-0.0.1/chiralfinder/_quadrupole/quadrupole_v3.py +179 -0
- chiralfinder-0.0.1/chiralfinder/_quadrupole/quadrupole_v4.py +94 -0
- chiralfinder-0.0.1/chiralfinder/_quadrupole/quadrupole_v5.py +174 -0
- chiralfinder-0.0.1/chiralfinder/_quadrupole/quadrupole_v6.py +44 -0
- chiralfinder-0.0.1/chiralfinder/_quadrupole/run.py +200 -0
- chiralfinder-0.0.1/pyproject.toml +38 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Meteor-han
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: chiralfinder
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: The chiralfinder package.
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: Chirality,Chiral axis
|
|
7
|
+
Author: Runhan Shi
|
|
8
|
+
Author-email: han.run.jiangming@sjtu.edu.cn
|
|
9
|
+
Maintainer: Runhan Shi
|
|
10
|
+
Maintainer-email: han.run.jiangming@sjtu.edu.cn
|
|
11
|
+
Requires-Python: >=3.8,<4.0
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.8
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Requires-Dist: numpy (>=1.26.4,<2.0.0)
|
|
21
|
+
Requires-Dist: rdkit (>=2024.3.3,<2025.0.0)
|
|
22
|
+
Requires-Dist: tqdm (>=4.65.0,<5.0.0)
|
|
23
|
+
Project-URL: Documentation, https://github.com/Meteor-han/chiralfinder/
|
|
24
|
+
Project-URL: Homepage, https://github.com/Meteor-han/chiralfinder/
|
|
25
|
+
Project-URL: Repository, https://github.com/Meteor-han/chiralfinder/
|
|
26
|
+
Project-URL: issues, https://github.com/Meteor-han/chiralfinder/issues
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# chiralfinder
|
|
30
|
+
|
|
31
|
+
Data and codes for the paper "ChiralFinder: A Computational Framework for Identifying Stereogenic Elements and Distinguishing Stereoisomers in Complex Molecules", in submission.
|
|
32
|
+
|
|
33
|
+
## Quick use
|
|
34
|
+
|
|
35
|
+
Install Anaconda, create and enter your own environment like
|
|
36
|
+
|
|
37
|
+
conda create -n env_test python=3.10
|
|
38
|
+
Enter the conda environment and install the ChiralFinder package through pip like
|
|
39
|
+
|
|
40
|
+
```
|
|
41
|
+
conda activate env_test
|
|
42
|
+
pip install chiralfinder
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Run `test.py` to get example results.
|
|
46
|
+
|
|
47
|
+
```
|
|
48
|
+
python test.py
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from chiralfinder import ChiralFinder
|
|
53
|
+
|
|
54
|
+
if __name__ == '__main__':
|
|
55
|
+
smi_list = ["C[C@H]1CC(=O)[C@]2(CCCC2=O)C1", "CC1=CC=C(SC2=C(C)N(C3=CC=CC=C3C(C)(C)C)C(C)=C2)C=C1"]
|
|
56
|
+
|
|
57
|
+
chiral_finder = ChiralFinder(smi_list, "SMILES")
|
|
58
|
+
res_ = chiral_finder.get_axial(n_cpus=8)
|
|
59
|
+
print(res_[0]["chiral axes"], res_[1]["chiral axes"])
|
|
60
|
+
chiral_finder.draw_res_axial("./img")
|
|
61
|
+
|
|
62
|
+
smi_list_center = ["BrC/C(=C\[C@@H]1CCCO1)C1CCCCC1"]
|
|
63
|
+
chiral_finder = ChiralFinder(smi_list_center, "SMILES")
|
|
64
|
+
res_ = chiral_finder.get_central()
|
|
65
|
+
print(res_)
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
You will get the images of two molecules with predicted chiral axes in the folder `./img` by default. Predicted chiral axes:
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
[(5,)] [(9, 10)]
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
<img src="https://github.com/Meteor-han/chiralfinder/blob/main/img_axial/0.png" alt="0" width="30%" height="auto" /><img src="https://github.com/Meteor-han/chiralfinder/blob/main/img_axial/1.png" alt="1" width="30%" height="auto" />
|
|
75
|
+
|
|
76
|
+
You will get the prediction of one molecule for central chirality.
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
[{
|
|
80
|
+
'center id': [4],
|
|
81
|
+
'quadrupole matrix':
|
|
82
|
+
[[array([[-0.29989323, -1.08474687, 0.09943544],
|
|
83
|
+
[-2.0754821 , 0.47857598, 1.02051223],
|
|
84
|
+
[-0.0064714 , -0.03258116, 2.29906673]])]],
|
|
85
|
+
'determinant': [[-5.501797575969392]],
|
|
86
|
+
'sign': [[-1.0]]
|
|
87
|
+
}]
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
## Dataset
|
|
92
|
+
|
|
93
|
+
The RotA dataset is stored in the folder `./data`. The excel file contains labeled chiral axes and some calculated molecular properties. The pickle file includes calculated molecular conformers.
|
|
94
|
+
|
|
95
|
+
We also provide sampled achiral molecules and centrally chiral molecules with multiple centers from the PubChem3D database in the folder `./data`.
|
|
96
|
+
|
|
97
|
+
## Citation
|
|
98
|
+
|
|
99
|
+
```
|
|
100
|
+
To be filled
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# chiralfinder
|
|
2
|
+
|
|
3
|
+
Data and codes for the paper "ChiralFinder: A Computational Framework for Identifying Stereogenic Elements and Distinguishing Stereoisomers in Complex Molecules", in submission.
|
|
4
|
+
|
|
5
|
+
## Quick use
|
|
6
|
+
|
|
7
|
+
Install Anaconda, create and enter your own environment like
|
|
8
|
+
|
|
9
|
+
conda create -n env_test python=3.10
|
|
10
|
+
Enter the conda environment and install the ChiralFinder package through pip like
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
conda activate env_test
|
|
14
|
+
pip install chiralfinder
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Run `test.py` to get example results.
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
python test.py
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from chiralfinder import ChiralFinder
|
|
25
|
+
|
|
26
|
+
if __name__ == '__main__':
|
|
27
|
+
smi_list = ["C[C@H]1CC(=O)[C@]2(CCCC2=O)C1", "CC1=CC=C(SC2=C(C)N(C3=CC=CC=C3C(C)(C)C)C(C)=C2)C=C1"]
|
|
28
|
+
|
|
29
|
+
chiral_finder = ChiralFinder(smi_list, "SMILES")
|
|
30
|
+
res_ = chiral_finder.get_axial(n_cpus=8)
|
|
31
|
+
print(res_[0]["chiral axes"], res_[1]["chiral axes"])
|
|
32
|
+
chiral_finder.draw_res_axial("./img")
|
|
33
|
+
|
|
34
|
+
smi_list_center = ["BrC/C(=C\[C@@H]1CCCO1)C1CCCCC1"]
|
|
35
|
+
chiral_finder = ChiralFinder(smi_list_center, "SMILES")
|
|
36
|
+
res_ = chiral_finder.get_central()
|
|
37
|
+
print(res_)
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
You will get the images of two molecules with predicted chiral axes in the folder `./img` by default. Predicted chiral axes:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
[(5,)] [(9, 10)]
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
<img src="https://github.com/Meteor-han/chiralfinder/blob/main/img_axial/0.png" alt="0" width="30%" height="auto" /><img src="https://github.com/Meteor-han/chiralfinder/blob/main/img_axial/1.png" alt="1" width="30%" height="auto" />
|
|
47
|
+
|
|
48
|
+
You will get the prediction of one molecule for central chirality.
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
[{
|
|
52
|
+
'center id': [4],
|
|
53
|
+
'quadrupole matrix':
|
|
54
|
+
[[array([[-0.29989323, -1.08474687, 0.09943544],
|
|
55
|
+
[-2.0754821 , 0.47857598, 1.02051223],
|
|
56
|
+
[-0.0064714 , -0.03258116, 2.29906673]])]],
|
|
57
|
+
'determinant': [[-5.501797575969392]],
|
|
58
|
+
'sign': [[-1.0]]
|
|
59
|
+
}]
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
## Dataset
|
|
64
|
+
|
|
65
|
+
The RotA dataset is stored in the folder `./data`. The excel file contains labeled chiral axes and some calculated molecular properties. The pickle file includes calculated molecular conformers.
|
|
66
|
+
|
|
67
|
+
We also provide sampled achiral molecules and centrally chiral molecules with multiple centers from the PubChem3D database in the folder `./data`.
|
|
68
|
+
|
|
69
|
+
## Citation
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
To be filled
|
|
73
|
+
```
|
|
74
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from ._quadrupole import (
|
|
2
|
+
ChiralCenter,
|
|
3
|
+
ChiralAxialType1,
|
|
4
|
+
ChiralAxialType2,
|
|
5
|
+
ChiralAxialType3,
|
|
6
|
+
ChiralAxialType4,
|
|
7
|
+
ChiralAxialType5,
|
|
8
|
+
ChiralAxialType6,
|
|
9
|
+
ChiralFinder
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
__all__ = (
|
|
14
|
+
"ChiralFinder",
|
|
15
|
+
"ChiralCenter",
|
|
16
|
+
"ChiralAxialType1",
|
|
17
|
+
"ChiralAxialType2",
|
|
18
|
+
"ChiralAxialType3",
|
|
19
|
+
"ChiralAxialType4",
|
|
20
|
+
"ChiralAxialType5",
|
|
21
|
+
"ChiralAxialType6"
|
|
22
|
+
)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from .quadrupole_center import ChiralCenter
|
|
2
|
+
from .quadrupole_v1 import ChiralAxialType1
|
|
3
|
+
from .quadrupole_v2 import ChiralAxialType2
|
|
4
|
+
from .quadrupole_v3 import ChiralAxialType3
|
|
5
|
+
from .quadrupole_v4 import ChiralAxialType4
|
|
6
|
+
from .quadrupole_v5 import ChiralAxialType5
|
|
7
|
+
from .quadrupole_v6 import ChiralAxialType6
|
|
8
|
+
from .run import ChiralFinder
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
__all__ = (
|
|
12
|
+
"ChiralFinder",
|
|
13
|
+
"ChiralCenter",
|
|
14
|
+
"ChiralAxialType1",
|
|
15
|
+
"ChiralAxialType2",
|
|
16
|
+
"ChiralAxialType3",
|
|
17
|
+
"ChiralAxialType4",
|
|
18
|
+
"ChiralAxialType5",
|
|
19
|
+
"ChiralAxialType6"
|
|
20
|
+
)
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
from .quadrupole_utils import *
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class ChiralCenter(ChiralBase):
|
|
5
|
+
def __init__(self, mol, mol_wo_Hs=None, CIP=True):
|
|
6
|
+
super().__init__(mol, mol_wo_Hs, CIP)
|
|
7
|
+
|
|
8
|
+
# get the chiral matrices
|
|
9
|
+
def get_chi_mat(self):
|
|
10
|
+
cen_chi = self.find_center_atoms()
|
|
11
|
+
|
|
12
|
+
mats, dets, norm_cp, signs = [], [], [], [] # for each conf
|
|
13
|
+
for i in cen_chi:
|
|
14
|
+
mat_confs = []
|
|
15
|
+
det_confs = []
|
|
16
|
+
norm_det_confs = []
|
|
17
|
+
sign_confs = []
|
|
18
|
+
for conf_ in self.coordinates:
|
|
19
|
+
atom = self.atoms[i]
|
|
20
|
+
neighbors = atom.GetNeighbors()
|
|
21
|
+
# (id_, rank_), sort by rank, increasing
|
|
22
|
+
neigh_id_rank = [(atom_.GetIdx(), self.CIP_list[atom_.GetIdx()]) for atom_ in neighbors]
|
|
23
|
+
neigh_id_rank = sorted(neigh_id_rank, key=lambda x: x[1])
|
|
24
|
+
|
|
25
|
+
# center, atom 1, 2, 3, 4
|
|
26
|
+
neigh_cor = [conf_[i]]
|
|
27
|
+
for one in neigh_id_rank:
|
|
28
|
+
neigh_cor.append(conf_[one[0]])
|
|
29
|
+
# get the matrix
|
|
30
|
+
if len(neigh_cor) == 4:
|
|
31
|
+
neigh_cor.insert(1, (neigh_cor[1]+neigh_cor[2]+neigh_cor[3]-neigh_cor[0]*3)/3*-1.0+neigh_cor[0])
|
|
32
|
+
if len(neigh_cor) < 5:
|
|
33
|
+
continue
|
|
34
|
+
a = neigh_cor[1] - neigh_cor[0]
|
|
35
|
+
b = neigh_cor[4] - neigh_cor[3]
|
|
36
|
+
c = neigh_cor[4] - neigh_cor[2]
|
|
37
|
+
cp_max = np.linalg.norm(np.cross(a, b)) * np.linalg.norm(c)
|
|
38
|
+
mat = np.array([a, b, c])
|
|
39
|
+
mat_confs.append(mat)
|
|
40
|
+
det_, sign_ = self.criterion(mat)
|
|
41
|
+
det_confs.append(det_)
|
|
42
|
+
norm_det_confs.append(det_/cp_max)
|
|
43
|
+
sign_confs.append(sign_)
|
|
44
|
+
mats.append(mat_confs)
|
|
45
|
+
dets.append(det_confs)
|
|
46
|
+
norm_cp.append(norm_det_confs)
|
|
47
|
+
signs.append(sign_confs)
|
|
48
|
+
return {"center id": cen_chi, "quadrupole matrix": mats, "determinant": dets, "norm CP": norm_cp, "sign": signs}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
from rdkit import Chem
|
|
2
|
+
from rdkit.Chem import AllChem
|
|
3
|
+
from copy import deepcopy
|
|
4
|
+
import numpy as np
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
import warnings
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ChiralBase:
|
|
10
|
+
def __init__(self, mol=None, mol_wo_Hs=None, max_conf_num=5, sign_eps=1e-4, CIP=True):
|
|
11
|
+
# with Hs
|
|
12
|
+
self.mol = mol
|
|
13
|
+
self.mol_wo_Hs = Chem.RemoveHs(mol) if mol_wo_Hs is None else mol_wo_Hs
|
|
14
|
+
self.atoms = mol.GetAtoms()
|
|
15
|
+
self.ssr = Chem.GetSymmSSSR(mol)
|
|
16
|
+
self.connection = Chem.GetAdjacencyMatrix(mol)
|
|
17
|
+
# coordinates, important; up to max_conf_num
|
|
18
|
+
self.coordinates = []
|
|
19
|
+
self.max_conf_num = max_conf_num
|
|
20
|
+
self.sign_eps = sign_eps
|
|
21
|
+
for conf in mol.GetConformers():
|
|
22
|
+
# np array
|
|
23
|
+
self.coordinates.append(conf.GetPositions())
|
|
24
|
+
if len(self.coordinates) >= max_conf_num:
|
|
25
|
+
break
|
|
26
|
+
# self.conformer = mol.GetConformer()
|
|
27
|
+
if CIP:
|
|
28
|
+
# dict is OK
|
|
29
|
+
# AssignStereochemistry may fail anyway, try AssignStereochemistryFrom3D
|
|
30
|
+
try:
|
|
31
|
+
Chem.AssignStereochemistryFrom3D(self.mol_wo_Hs)
|
|
32
|
+
self.CIP_list = defaultdict(lambda: -1)
|
|
33
|
+
for atom in self.mol_wo_Hs.GetAtoms():
|
|
34
|
+
self.CIP_list[atom.GetIdx()] = int(atom.GetProp('_CIPRank'))
|
|
35
|
+
except:
|
|
36
|
+
warnings.warn("Fail to assign CIPs, use CanonicalRankAtoms instead.")
|
|
37
|
+
# just an order, not CIP, unable to relate to R/S
|
|
38
|
+
self.CIP_list = list(Chem.CanonicalRankAtoms(
|
|
39
|
+
mol, breakTies=False, includeChirality=True, includeIsotopes=True))
|
|
40
|
+
else:
|
|
41
|
+
# just an order, not CIP, unable to relate to R/S
|
|
42
|
+
self.CIP_list = list(Chem.CanonicalRankAtoms(
|
|
43
|
+
mol, breakTies=False, includeChirality=True, includeIsotopes=True))
|
|
44
|
+
|
|
45
|
+
# find chiral atoms
|
|
46
|
+
# with Hs, may get false center C "?"
|
|
47
|
+
def find_center_atoms(self):
|
|
48
|
+
c0 = Chem.FindMolChiralCenters(self.mol_wo_Hs, useLegacyImplementation=False)
|
|
49
|
+
cen_chi = [i[0] for i in c0]
|
|
50
|
+
return cen_chi
|
|
51
|
+
|
|
52
|
+
# find spiral atoms
|
|
53
|
+
def find_spiral_atoms(self):
|
|
54
|
+
# find all atoms shared by two rings
|
|
55
|
+
# for some molecules with Hs, "RingInfo not initialized"
|
|
56
|
+
ri = self.mol_wo_Hs.GetRingInfo()
|
|
57
|
+
spi_pot = []
|
|
58
|
+
for atom in self.atoms:
|
|
59
|
+
if ri.NumAtomRings(atom.GetIdx()) == 2:
|
|
60
|
+
spi_pot.append(atom.GetIdx())
|
|
61
|
+
|
|
62
|
+
# delete all atoms who share completely same rings with their neighbors
|
|
63
|
+
ssr = Chem.GetSymmSSSR(self.mol)
|
|
64
|
+
spi = deepcopy(spi_pot)
|
|
65
|
+
for j in spi_pot:
|
|
66
|
+
j_ring = []
|
|
67
|
+
for ring in ssr:
|
|
68
|
+
if j in ring:
|
|
69
|
+
j_ring.append(list(ring))
|
|
70
|
+
j_ring = j_ring[0] + j_ring[1]
|
|
71
|
+
|
|
72
|
+
neighbors = [atom.GetIdx() for atom in self.atoms[j].GetNeighbors()]
|
|
73
|
+
for k in neighbors:
|
|
74
|
+
if j_ring.count(k) == 2:
|
|
75
|
+
spi.remove(j)
|
|
76
|
+
break
|
|
77
|
+
return spi
|
|
78
|
+
|
|
79
|
+
# whether two atoms share one ring
|
|
80
|
+
def pub_ring(self, atom_1, atom_2):
|
|
81
|
+
atom_1_ring = []
|
|
82
|
+
# atom_1 rings
|
|
83
|
+
for ring in self.ssr:
|
|
84
|
+
if atom_1 in list(ring):
|
|
85
|
+
atom_1_ring.extend(list(ring))
|
|
86
|
+
# atom_2
|
|
87
|
+
return True if atom_2 in set(atom_1_ring) else False
|
|
88
|
+
|
|
89
|
+
# find 'C=C' like double bonds
|
|
90
|
+
def get_double_bond(self):
|
|
91
|
+
bonds = self.mol.GetBonds()
|
|
92
|
+
double_bonds = []
|
|
93
|
+
for bond in bonds:
|
|
94
|
+
if bond.GetBondType() == Chem.rdchem.BondType.DOUBLE:
|
|
95
|
+
double_bonds.append(bond)
|
|
96
|
+
return double_bonds
|
|
97
|
+
|
|
98
|
+
# whether two bonds share one atom
|
|
99
|
+
def pub_atom(self, bond_1_, bond_2_):
|
|
100
|
+
atom_1_id = [bond_1_.GetBeginAtomIdx(), bond_1_.GetEndAtomIdx()]
|
|
101
|
+
atom_2_id = [bond_2_.GetBeginAtomIdx(), bond_2_.GetEndAtomIdx()]
|
|
102
|
+
criterion = set(atom_1_id + atom_2_id)
|
|
103
|
+
return True if len(criterion) == 3 else False
|
|
104
|
+
|
|
105
|
+
# compute the absolute chirality
|
|
106
|
+
def criterion(self, mat):
|
|
107
|
+
det_ = np.linalg.det(mat)
|
|
108
|
+
sign_ = np.sign(det_) if abs(det_) > self.sign_eps else 0
|
|
109
|
+
return det_, sign_
|
|
110
|
+
|
|
111
|
+
def check_chain_equal(self, bonds_chain):
|
|
112
|
+
bonds2idx = []
|
|
113
|
+
for one_ in bonds_chain:
|
|
114
|
+
temp = []
|
|
115
|
+
for bond in one_:
|
|
116
|
+
temp.append(sorted([bond.GetBeginAtomIdx(), bond.GetEndAtomIdx()]))
|
|
117
|
+
bonds2idx.append(sorted(temp))
|
|
118
|
+
return sorted(bonds2idx)
|
|
119
|
+
|
|
120
|
+
# add Hs, retain existing coor, xy axis reverse coor
|
|
121
|
+
def transform_coordinate(mol_):
|
|
122
|
+
# if no conformer, try to embed
|
|
123
|
+
if mol_.GetNumConformers() == 0:
|
|
124
|
+
AllChem.EmbedMolecule(mol_, maxAttempts=100)
|
|
125
|
+
mol = Chem.AddHs(mol_, addCoords=True)
|
|
126
|
+
# copy
|
|
127
|
+
mol_r = Chem.Mol(mol)
|
|
128
|
+
mol_r.RemoveAllConformers()
|
|
129
|
+
num_atoms = mol.GetNumAtoms()
|
|
130
|
+
conf_num = mol.GetNumConformers()
|
|
131
|
+
assert conf_num > 0.5
|
|
132
|
+
for i in range(conf_num):
|
|
133
|
+
c = mol.GetConformer(i)
|
|
134
|
+
p = c.GetPositions()
|
|
135
|
+
p_r = np.copy(p)
|
|
136
|
+
p_r[:, 2] = -p_r[:, 2]
|
|
137
|
+
_conformer = Chem.Conformer(num_atoms)
|
|
138
|
+
for j in range(len(p_r)):
|
|
139
|
+
_conformer.SetAtomPosition(j, p_r[j])
|
|
140
|
+
mol_r.AddConformer(_conformer, assignId=True)
|
|
141
|
+
|
|
142
|
+
return mol, mol_r
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# Quadrupole Matrix, spiral atom
|
|
2
|
+
from .quadrupole_utils import *
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class ChiralAxialType1(ChiralBase):
|
|
6
|
+
"""axial spiral (1 atom)"""
|
|
7
|
+
def __init__(self, mol):
|
|
8
|
+
super().__init__(mol)
|
|
9
|
+
|
|
10
|
+
# find spiral atoms with "axial chirality"
|
|
11
|
+
def find_chi_spi(self, spi_):
|
|
12
|
+
chi_spi = []
|
|
13
|
+
ssr = Chem.GetSymmSSSR(self.mol)
|
|
14
|
+
for i in spi_:
|
|
15
|
+
# get the rings where spiral atoms are in
|
|
16
|
+
i_ring = []
|
|
17
|
+
for ring in ssr:
|
|
18
|
+
if i in ring:
|
|
19
|
+
i_ring.append(list(ring))
|
|
20
|
+
# classify the spiral atom's neighbors with rings
|
|
21
|
+
nei_1 = self.atoms[i].GetNeighbors()
|
|
22
|
+
nei_1 = [x.GetIdx() for x in nei_1]
|
|
23
|
+
raw_nei_1 = deepcopy(nei_1)
|
|
24
|
+
nei_2 = []
|
|
25
|
+
for j in range(len(raw_nei_1)):
|
|
26
|
+
if raw_nei_1[j] in i_ring[0]:
|
|
27
|
+
nei_2.append(raw_nei_1[j])
|
|
28
|
+
nei_1.remove(raw_nei_1[j])
|
|
29
|
+
# whether neighbors who are in the same ring are equivalent
|
|
30
|
+
if (nei_1[0] - nei_1[1]) * (nei_2[0] - nei_2[1]) != 0:
|
|
31
|
+
chi_spi.append([i, nei_1, nei_2])
|
|
32
|
+
|
|
33
|
+
return chi_spi
|
|
34
|
+
|
|
35
|
+
# get the chiral matrices
|
|
36
|
+
def get_chi_mat(self):
|
|
37
|
+
chi_spi_ = self.find_chi_spi(self.find_spiral_atoms())
|
|
38
|
+
|
|
39
|
+
mats, dets, norm_cp, signs = [], [], [], [] # for each conf
|
|
40
|
+
for one in chi_spi_:
|
|
41
|
+
# (id_, rank_), sort by rank, increasing
|
|
42
|
+
nei_1_id_rank = [(i, self.CIP_list[self.atoms[i].GetIdx()]) for i in one[1]]
|
|
43
|
+
nei_2_id_rank = [(i, self.CIP_list[self.atoms[i].GetIdx()]) for i in one[2]]
|
|
44
|
+
nei_1_id_rank = sorted(nei_1_id_rank, key=lambda x: x[1])
|
|
45
|
+
nei_2_id_rank = sorted(nei_2_id_rank, key=lambda x: x[1])
|
|
46
|
+
neigh_id_rank = nei_1_id_rank + nei_2_id_rank
|
|
47
|
+
|
|
48
|
+
mat_confs = []
|
|
49
|
+
det_confs = []
|
|
50
|
+
norm_det_confs = []
|
|
51
|
+
sign_confs = []
|
|
52
|
+
for conf_ in self.coordinates:
|
|
53
|
+
neigh_cor = [conf_[one[0]]]
|
|
54
|
+
for one_ in neigh_id_rank:
|
|
55
|
+
neigh_cor.append(conf_[one_[0]])
|
|
56
|
+
# get the matrix
|
|
57
|
+
a = neigh_cor[1] - neigh_cor[0]
|
|
58
|
+
b = neigh_cor[2] - neigh_cor[0]
|
|
59
|
+
c = neigh_cor[3] - neigh_cor[4]
|
|
60
|
+
cp_max = np.linalg.norm(np.cross(a, b)) * np.linalg.norm(c)
|
|
61
|
+
mat = np.array([a, b, c])
|
|
62
|
+
mat_confs.append(mat)
|
|
63
|
+
det_, sign_ = self.criterion(mat)
|
|
64
|
+
det_confs.append(det_)
|
|
65
|
+
norm_det_confs.append(det_/cp_max)
|
|
66
|
+
sign_confs.append(sign_)
|
|
67
|
+
mats.append(mat_confs)
|
|
68
|
+
dets.append(det_confs)
|
|
69
|
+
norm_cp.append(norm_det_confs)
|
|
70
|
+
signs.append(sign_confs)
|
|
71
|
+
return {"spiral id": chi_spi_, "chiral axes": [(one[0],) for one in chi_spi_], "quadrupole matrix": mats,
|
|
72
|
+
"determinant": dets, "norm CP": norm_cp, "sign": signs}
|