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.
Files changed (32) hide show
  1. {aamutils-0.0.4 → aamutils-0.0.6}/PKG-INFO +1 -1
  2. aamutils-0.0.6/aamutils/__init__.py +1 -0
  3. {aamutils-0.0.4 → aamutils-0.0.6}/aamutils/algorithm/ilp.py +47 -2
  4. aamutils-0.0.6/aamutils/mapping.py +56 -0
  5. {aamutils-0.0.4 → aamutils-0.0.6}/aamutils/utils.py +12 -7
  6. {aamutils-0.0.4 → aamutils-0.0.6}/pyproject.toml +1 -1
  7. {aamutils-0.0.4 → aamutils-0.0.6}/test/algorithm/test_ilp.py +1 -25
  8. aamutils-0.0.6/test/test_mapping.py +23 -0
  9. aamutils-0.0.4/test/algorithm/__init__.py +0 -0
  10. {aamutils-0.0.4 → aamutils-0.0.6}/.github/workflows/publish-package.yml +0 -0
  11. {aamutils-0.0.4 → aamutils-0.0.6}/.github/workflows/test-and-lint.yml +0 -0
  12. {aamutils-0.0.4 → aamutils-0.0.6}/.gitignore +0 -0
  13. {aamutils-0.0.4 → aamutils-0.0.6}/LICENSE +0 -0
  14. {aamutils-0.0.4 → aamutils-0.0.6}/Makefile +0 -0
  15. {aamutils-0.0.4 → aamutils-0.0.6}/README.md +0 -0
  16. {aamutils-0.0.4 → aamutils-0.0.6}/aamutils/__main__.py +0 -0
  17. {aamutils-0.0.4/aamutils → aamutils-0.0.6/aamutils/algorithm}/__init__.py +0 -0
  18. {aamutils-0.0.4 → aamutils-0.0.6}/aamutils/algorithm/aaming.py +0 -0
  19. {aamutils-0.0.4 → aamutils-0.0.6}/aamutils/cmd/__init__.py +0 -0
  20. {aamutils-0.0.4 → aamutils-0.0.6}/aamutils/cmd/expanding.py +0 -0
  21. {aamutils-0.0.4 → aamutils-0.0.6}/benchmark.py +0 -0
  22. {aamutils-0.0.4 → aamutils-0.0.6}/conf.py +0 -0
  23. {aamutils-0.0.4 → aamutils-0.0.6}/data/test_dataset.json +0 -0
  24. {aamutils-0.0.4 → aamutils-0.0.6}/doc/getting_started.rst +0 -0
  25. {aamutils-0.0.4 → aamutils-0.0.6}/doc/reference.rst +0 -0
  26. {aamutils-0.0.4 → aamutils-0.0.6}/index.rst +0 -0
  27. {aamutils-0.0.4 → aamutils-0.0.6}/lint.sh +0 -0
  28. {aamutils-0.0.4 → aamutils-0.0.6}/make.bat +0 -0
  29. {aamutils-0.0.4 → aamutils-0.0.6}/requirements.txt +0 -0
  30. {aamutils-0.0.4/aamutils/algorithm → aamutils-0.0.6/test}/__init__.py +0 -0
  31. {aamutils-0.0.4/test → aamutils-0.0.6/test/algorithm}/__init__.py +0 -0
  32. {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.4
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
- "g_indicator_constraint_lower_{}-{}".format(i, j),
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
- "s_indicator_constraint_lower_{}-{}".format(i, j),
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(its, ax, bond_key="bond", aam_key="aam", symbol_key="symbol"):
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
- positions = {}
156
- conformer = rdDepictor.Compute2DCoords(mol)
157
- for i, atom in enumerate(mol.GetAtoms()):
158
- aam = atom.GetAtomMapNum()
159
- apos = mol.GetConformer(conformer).GetAtomPosition(i)
160
- positions[aam] = [apos.x, apos.y]
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")
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "aamutils"
7
- version = "0.0.4"
7
+ version = "0.0.6"
8
8
  authors = [
9
9
  {name="Klaus Weinbauer", email="klaus@bioinf.uni-leipzig.de"}
10
10
  ]
@@ -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.zeros((4, 4))
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