aamutils 0.0.4__tar.gz → 0.0.6__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.
- {aamutils-0.0.4 → aamutils-0.0.6}/PKG-INFO +1 -1
- aamutils-0.0.6/aamutils/__init__.py +1 -0
- {aamutils-0.0.4 → aamutils-0.0.6}/aamutils/algorithm/ilp.py +47 -2
- aamutils-0.0.6/aamutils/mapping.py +56 -0
- {aamutils-0.0.4 → aamutils-0.0.6}/aamutils/utils.py +12 -7
- {aamutils-0.0.4 → aamutils-0.0.6}/pyproject.toml +1 -1
- {aamutils-0.0.4 → aamutils-0.0.6}/test/algorithm/test_ilp.py +1 -25
- aamutils-0.0.6/test/test_mapping.py +23 -0
- aamutils-0.0.4/test/algorithm/__init__.py +0 -0
- {aamutils-0.0.4 → aamutils-0.0.6}/.github/workflows/publish-package.yml +0 -0
- {aamutils-0.0.4 → aamutils-0.0.6}/.github/workflows/test-and-lint.yml +0 -0
- {aamutils-0.0.4 → aamutils-0.0.6}/.gitignore +0 -0
- {aamutils-0.0.4 → aamutils-0.0.6}/LICENSE +0 -0
- {aamutils-0.0.4 → aamutils-0.0.6}/Makefile +0 -0
- {aamutils-0.0.4 → aamutils-0.0.6}/README.md +0 -0
- {aamutils-0.0.4 → aamutils-0.0.6}/aamutils/__main__.py +0 -0
- {aamutils-0.0.4/aamutils → aamutils-0.0.6/aamutils/algorithm}/__init__.py +0 -0
- {aamutils-0.0.4 → aamutils-0.0.6}/aamutils/algorithm/aaming.py +0 -0
- {aamutils-0.0.4 → aamutils-0.0.6}/aamutils/cmd/__init__.py +0 -0
- {aamutils-0.0.4 → aamutils-0.0.6}/aamutils/cmd/expanding.py +0 -0
- {aamutils-0.0.4 → aamutils-0.0.6}/benchmark.py +0 -0
- {aamutils-0.0.4 → aamutils-0.0.6}/conf.py +0 -0
- {aamutils-0.0.4 → aamutils-0.0.6}/data/test_dataset.json +0 -0
- {aamutils-0.0.4 → aamutils-0.0.6}/doc/getting_started.rst +0 -0
- {aamutils-0.0.4 → aamutils-0.0.6}/doc/reference.rst +0 -0
- {aamutils-0.0.4 → aamutils-0.0.6}/index.rst +0 -0
- {aamutils-0.0.4 → aamutils-0.0.6}/lint.sh +0 -0
- {aamutils-0.0.4 → aamutils-0.0.6}/make.bat +0 -0
- {aamutils-0.0.4 → aamutils-0.0.6}/requirements.txt +0 -0
- {aamutils-0.0.4/aamutils/algorithm → aamutils-0.0.6/test}/__init__.py +0 -0
- {aamutils-0.0.4/test → aamutils-0.0.6/test/algorithm}/__init__.py +0 -0
- {aamutils-0.0.4 → aamutils-0.0.6}/test/algorithm/test_aaming.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: aamutils
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.6
|
|
4
4
|
Summary: Collection of atom-atom-mapping utility functions.
|
|
5
5
|
Project-URL: homepage, https://github.com/klausweinbauer/AAMUtils
|
|
6
6
|
Project-URL: source, https://github.com/klausweinbauer/AAMUtils
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .mapping import Mapper
|
|
@@ -57,19 +57,48 @@ def _indicator_constraint(problem, D, G, S, k):
|
|
|
57
57
|
for j in range(m):
|
|
58
58
|
problem += (
|
|
59
59
|
D[i, j] <= k * G[i, j],
|
|
60
|
-
"
|
|
60
|
+
"g_indicator_constraint1_{}-{}".format(i, j),
|
|
61
|
+
)
|
|
62
|
+
problem += (
|
|
63
|
+
D[i, j] >= -k + G[i, j] * (k + 1),
|
|
64
|
+
"g_indicator_constraint2_{}-{}".format(i, j),
|
|
61
65
|
)
|
|
62
66
|
problem += (
|
|
63
67
|
-D[i, j] <= k * S[i, j],
|
|
64
|
-
"
|
|
68
|
+
"s_indicator_constraint1_{}-{}".format(i, j),
|
|
69
|
+
)
|
|
70
|
+
problem += (
|
|
71
|
+
-D[i, j] >= -k + S[i, j] * (k + 1),
|
|
72
|
+
"s_indicator_constraint2_{}-{}".format(i, j),
|
|
65
73
|
)
|
|
66
74
|
|
|
67
75
|
|
|
76
|
+
def _expected_rc_atoms_constraint(problem, G, S, expected_rc_atoms):
|
|
77
|
+
m = int(np.sqrt(len(G.keys())))
|
|
78
|
+
assert m == int(np.sqrt(len(S.keys())))
|
|
79
|
+
|
|
80
|
+
lp_io = lp.LpVariable.dicts("io", [i for i in range(m)], cat=lp.LpInteger)
|
|
81
|
+
lp_rc = lp.LpVariable.dicts("rc", [i for i in range(m)], cat=lp.LpBinary)
|
|
82
|
+
for i in range(m):
|
|
83
|
+
problem += (
|
|
84
|
+
lp_io[i] == lp.lpSum([G[i, j] + S[i, j] for j in range(m)]),
|
|
85
|
+
"idx_io_sum_{}".format(i),
|
|
86
|
+
)
|
|
87
|
+
problem += (lp_io[i] <= m * lp_rc[i], "is_rc_indicator1_{}".format(i))
|
|
88
|
+
problem += (lp_io[i] >= lp_rc[i], "is_rc_indicator_{}".format(i))
|
|
89
|
+
|
|
90
|
+
problem += (
|
|
91
|
+
expected_rc_atoms == lp.lpSum([lp_rc[i] for i in range(m)]),
|
|
92
|
+
"rc_atom_constraint",
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
|
|
68
96
|
def expand_partial_aam_balanced(
|
|
69
97
|
G: nx.Graph,
|
|
70
98
|
H: nx.Graph,
|
|
71
99
|
beta_map: None | list[tuple[int, int, int]] = None,
|
|
72
100
|
bond_key="bond",
|
|
101
|
+
expected_rc: None | tuple[None | int, None | int] = None,
|
|
73
102
|
) -> tuple[np.ndarray, str, float]:
|
|
74
103
|
"""Function to extend the partial atom-atom map beta of the balanced
|
|
75
104
|
reaction G \u2192 H to a full atom-atom map based on the Minimal Chemical
|
|
@@ -81,6 +110,14 @@ def expand_partial_aam_balanced(
|
|
|
81
110
|
specified the existing mapping in G and H will be used.
|
|
82
111
|
:param bond_key: (optional) The edge label in G and H which encodes the
|
|
83
112
|
bond order.
|
|
113
|
+
:param expected_rc: (optional) Configuration for the expected reaction
|
|
114
|
+
center. The first parameter in the tuple is the number of atoms and
|
|
115
|
+
the second parameter is the number of bonds. The atoms and bonds are
|
|
116
|
+
only constraint if the value is not None. It is possible to constraint
|
|
117
|
+
only atoms or only bonds. Constraining the expected reaction center can
|
|
118
|
+
help to get correct AAM for reactions where the Minimal Chemical
|
|
119
|
+
Distance is unsuitable, e.g. for a Diels-Alder reaction you can set
|
|
120
|
+
expected_rc to (6, 6).
|
|
84
121
|
|
|
85
122
|
:returns: A 3-tuple. The first element is the mapping matrix X. The second
|
|
86
123
|
element is the status string of the ILP solver (e.g. 'Optimal') and the
|
|
@@ -134,6 +171,14 @@ def expand_partial_aam_balanced(
|
|
|
134
171
|
lp_f == lp.lpSum([lp_G[i, j] + lp_S[i, j] for i in range(m) for j in range(m)]),
|
|
135
172
|
"objective",
|
|
136
173
|
)
|
|
174
|
+
if expected_rc is not None:
|
|
175
|
+
if not isinstance(expected_rc, tuple) or len(expected_rc) != 2:
|
|
176
|
+
raise ValueError("Argument expected_rc must be a tuple of size 2.")
|
|
177
|
+
expected_rc_atoms, expected_rc_bonds = expected_rc
|
|
178
|
+
if expected_rc_bonds is not None:
|
|
179
|
+
problem += (lp_f == 2 * expected_rc_bonds, "rc_bond_constraint")
|
|
180
|
+
if expected_rc_atoms is not None:
|
|
181
|
+
_expected_rc_atoms_constraint(problem, lp_G, lp_S, expected_rc_atoms)
|
|
137
182
|
|
|
138
183
|
_bijection_constraint(problem, lp_X)
|
|
139
184
|
_beta_constraint(problem, lp_X, beta_map)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import rdkit.Chem.rdmolops as rdmolops
|
|
2
|
+
import rdkit.Chem.rdmolfiles as rdmolfiles
|
|
3
|
+
|
|
4
|
+
from aamutils.utils import graph_to_mol, mol_to_graph, set_aam
|
|
5
|
+
from aamutils.algorithm.ilp import expand_partial_aam_balanced
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Mapper:
|
|
9
|
+
def __init__(self, include_H=False):
|
|
10
|
+
self.include_H = include_H
|
|
11
|
+
|
|
12
|
+
def _map_rxn(self, rxn_smiles: str, expected_rc):
|
|
13
|
+
g_smiles, h_smiles = rxn_smiles.split(">>")
|
|
14
|
+
g_mol = rdmolfiles.MolFromSmiles(g_smiles)
|
|
15
|
+
h_mol = rdmolfiles.MolFromSmiles(h_smiles)
|
|
16
|
+
if self.include_H:
|
|
17
|
+
g_mol = rdmolops.AddHs(g_mol, 0)
|
|
18
|
+
h_mol = rdmolops.AddHs(h_mol, 0)
|
|
19
|
+
G = mol_to_graph(g_mol)
|
|
20
|
+
H = mol_to_graph(h_mol)
|
|
21
|
+
|
|
22
|
+
M, status, e_diff = expand_partial_aam_balanced(G, H, expected_rc=expected_rc)
|
|
23
|
+
if status != "Optimal":
|
|
24
|
+
return None
|
|
25
|
+
|
|
26
|
+
set_aam(G, H, M)
|
|
27
|
+
|
|
28
|
+
g_mol = graph_to_mol(G)
|
|
29
|
+
h_mol = graph_to_mol(H)
|
|
30
|
+
|
|
31
|
+
aam_smiles = "{}>>{}".format(
|
|
32
|
+
rdmolfiles.MolToSmiles(g_mol), rdmolfiles.MolToSmiles(h_mol)
|
|
33
|
+
)
|
|
34
|
+
return aam_smiles
|
|
35
|
+
|
|
36
|
+
def get_aam(self, smiles: str | list[str], expected_rc=None) -> str | list[str]:
|
|
37
|
+
return_list = True
|
|
38
|
+
if isinstance(smiles, str):
|
|
39
|
+
smiles = [smiles]
|
|
40
|
+
return_list = False
|
|
41
|
+
if expected_rc is None:
|
|
42
|
+
expected_rc = [None for _ in range(len(smiles))]
|
|
43
|
+
elif not isinstance(expected_rc, list):
|
|
44
|
+
expected_rc = [expected_rc]
|
|
45
|
+
if len(smiles) != len(expected_rc):
|
|
46
|
+
raise ValueError(
|
|
47
|
+
"Argument min_bonds must be of same length ({}) as smiles.".format(
|
|
48
|
+
len(smiles)
|
|
49
|
+
)
|
|
50
|
+
)
|
|
51
|
+
aam_smiles = []
|
|
52
|
+
for smiles, exp_rc in zip(smiles, expected_rc):
|
|
53
|
+
aam_smiles.append(self._map_rxn(smiles, expected_rc=exp_rc))
|
|
54
|
+
if not return_list:
|
|
55
|
+
return aam_smiles[0]
|
|
56
|
+
return aam_smiles
|
|
@@ -148,16 +148,21 @@ def its2mol(its: nx.Graph, aam_key="aam", bond_key="bond") -> Chem.rdchem.Mol:
|
|
|
148
148
|
return graph_to_mol(_its)
|
|
149
149
|
|
|
150
150
|
|
|
151
|
-
def plot_its(
|
|
151
|
+
def plot_its(
|
|
152
|
+
its, ax, bond_key="bond", aam_key="aam", symbol_key="symbol", use_mol_coords=True
|
|
153
|
+
):
|
|
152
154
|
bond_char = {None: "∅", 1: "—", 2: "=", 3: "≡"}
|
|
153
155
|
mol = its2mol(its, aam_key=aam_key, bond_key=bond_key)
|
|
154
156
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
157
|
+
if use_mol_coords:
|
|
158
|
+
positions = {}
|
|
159
|
+
conformer = rdDepictor.Compute2DCoords(mol)
|
|
160
|
+
for i, atom in enumerate(mol.GetAtoms()):
|
|
161
|
+
aam = atom.GetAtomMapNum()
|
|
162
|
+
apos = mol.GetConformer(conformer).GetAtomPosition(i)
|
|
163
|
+
positions[aam] = [apos.x, apos.y]
|
|
164
|
+
else:
|
|
165
|
+
positions = nx.spring_layout(its)
|
|
161
166
|
|
|
162
167
|
ax.axis("equal")
|
|
163
168
|
ax.axis("off")
|
|
@@ -34,26 +34,6 @@ def test_example1():
|
|
|
34
34
|
np.testing.assert_array_equal(X, exp_X)
|
|
35
35
|
|
|
36
36
|
|
|
37
|
-
def test_example1_without_aam():
|
|
38
|
-
# The tested mapping is not correct. This is to show equal solutions based
|
|
39
|
-
# on the implemented optimization criterion. The correct mapping is in
|
|
40
|
-
# test_example1()
|
|
41
|
-
mol_G = rdmolfiles.MolFromSmiles("CCC[Cl].[N]")
|
|
42
|
-
mol_H = rdmolfiles.MolFromSmiles("CCC[N].[Cl]")
|
|
43
|
-
exp_X = np.zeros((5, 5))
|
|
44
|
-
exp_X[0, 2] = 1
|
|
45
|
-
exp_X[1, 1] = 1
|
|
46
|
-
exp_X[2, 0] = 1
|
|
47
|
-
exp_X[3, 4] = 1
|
|
48
|
-
exp_X[4, 3] = 1
|
|
49
|
-
G = mol_to_graph(mol_G)
|
|
50
|
-
H = mol_to_graph(mol_H)
|
|
51
|
-
X, status, v = expand_partial_aam_balanced(G, H)
|
|
52
|
-
assert 4 == v
|
|
53
|
-
assert "Optimal" == status
|
|
54
|
-
np.testing.assert_array_equal(X, exp_X)
|
|
55
|
-
|
|
56
|
-
|
|
57
37
|
def test_example2_m1():
|
|
58
38
|
mol_G = rdmolfiles.MolFromSmiles("[C:1][C:2]=[C:3][C:4]")
|
|
59
39
|
mol_H = rdmolfiles.MolFromSmiles("[C:1].[C:2]=[C:4][C:3]")
|
|
@@ -73,11 +53,7 @@ def test_example2_m1():
|
|
|
73
53
|
def test_example2_m2():
|
|
74
54
|
mol_G = rdmolfiles.MolFromSmiles("CC=CC")
|
|
75
55
|
mol_H = rdmolfiles.MolFromSmiles("C.C=CC")
|
|
76
|
-
exp_X = np.
|
|
77
|
-
exp_X[0, 3] = 1
|
|
78
|
-
exp_X[1, 2] = 1
|
|
79
|
-
exp_X[2, 1] = 1
|
|
80
|
-
exp_X[3, 0] = 1
|
|
56
|
+
exp_X = np.eye(4)
|
|
81
57
|
G = mol_to_graph(mol_G)
|
|
82
58
|
H = mol_to_graph(mol_H)
|
|
83
59
|
X, status, v = expand_partial_aam_balanced(G, H)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from aamutils.mapping import Mapper
|
|
2
|
+
from aamutils.algorithm.aaming import get_its, get_rc
|
|
3
|
+
from aamutils.utils import smiles_to_graph
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def test_mapper_with_expected_rc():
|
|
7
|
+
# Diels-Alder reaction example
|
|
8
|
+
smiles = "C1=CC=CC1.C1=CC=CC1>>C1=CC2CC1C3C2C=CC3"
|
|
9
|
+
mapper = Mapper()
|
|
10
|
+
aam_smiles = mapper.get_aam(smiles, expected_rc=(6, 6))
|
|
11
|
+
assert isinstance(aam_smiles, str)
|
|
12
|
+
g, h = smiles_to_graph(aam_smiles)
|
|
13
|
+
its = get_its(g, h)
|
|
14
|
+
rc = get_rc(its)
|
|
15
|
+
assert len(rc.nodes) == 6
|
|
16
|
+
assert len(rc.edges) == 6
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def test_mapper_infeasable():
|
|
20
|
+
smiles = "C>>C"
|
|
21
|
+
mapper = Mapper()
|
|
22
|
+
aam_smiles = mapper.get_aam(smiles, expected_rc=(2, 2))
|
|
23
|
+
assert aam_smiles is None
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|