Deterministic kekulize, independent of atom and bond order (#9125)

* Make kekulization deterministic

* Add tautomer order-independence regression (python)

* Adjust tautomer tests for deterministic kekulization

* Update graphmol wedged-bond kekulization checks

* SmilesParse: update aromatic bond index expectations

* SmilesParse: refresh cxsmilesTest expected files

* Depictor: update testDepictor expected MolBlocks

* Depictor: update depictorCatch expectations

* Depictor Wrap: update expected MolBlock for pyDepictor

* MarvinParse: update testMrvToMol expected outputs

* FileParsers: refresh testAtropisomers expected outputs

* FileParsers: update tests for deterministic kekulization

* MolDraw2D: refresh brittle bond assertions

* RascalMCES: update expected cluster size

* MinimalLib: make cffi wedging check order-independent

* documentation fix

* MinimalLib: update Kekulé bond table in aligned-coords test

* Hoist duplicated lambdas to TEST_CASE scope

* Remove unused originalWedges variable

* Remove redundant bounds check; clarify wedge-end preference

* Pre-sort allAtms by wedge-end + rank

* Use mol.atomNeighbors() for neighbor iteration

* Check inAllAtms before linear-scanning done

* Drop redundant optsV/wedgedOptsV sorts

* Remove unused Canon.h include

* Add canonical parameter to Kekulize; skip ranking during sanitization

* Test canonical re-kekulization preserves stereo across atom orderings

* MinimalLib: update Kekulé bond orders in invertedWedges

* Change Kekulize canonical default to false, expose in Python wrappers

* keep rank order, push_back

* Revert "RascalMCES: update expected cluster size"

This reverts commit a81bb39495.

* docstring change

* expose new flag to python wrapper

* document changes in ReleaseNotes.md

* revert minimallib test changes again

* canonical = true defaults

* Revert "revert minimallib test changes again"

This reverts commit 039e1d84da.

* Reapply "RascalMCES: update expected cluster size"

This reverts commit 7b83a7a3e8.

---------

Co-authored-by: greg landrum <greg.landrum@gmail.com>
This commit is contained in:
Yakov Pechersky
2026-03-19 03:43:13 -04:00
committed by GitHub
parent 67b4555611
commit 0986d22c58
254 changed files with 2053 additions and 1785 deletions

View File

@@ -195,6 +195,30 @@ double angle_deg_between_vectors(double *v1, double *v2) {
(v2[0] * v2[0] + v2[1] * v2[1])));
}
static int molblock_has_wedge_lines(const char *molblock,
const char *expected_bond_table) {
// expected_bond_table is a list of bond lines like:
// " 2 1 1 6\n 2 3 1 0\n ...".
// Only require that each wedge/dash bond line (bond_dir != 0) appears
// somewhere in the molblock. This is order-independent but still brittle.
if (!molblock || !expected_bond_table) {
return 0;
}
char *dup = strdup(expected_bond_table);
assert(dup);
for (char *line = strtok(dup, "\n"); line; line = strtok(NULL, "\n")) {
int a1 = 0, a2 = 0, btype = 0, bdir = 0;
if (sscanf(line, "%d %d %d %d", &a1, &a2, &btype, &bdir) == 4 && bdir != 0) {
if (!strstr(molblock, line)) {
free(dup);
return 0;
}
}
}
free(dup);
return 1;
}
void test_io() {
char *pkl;
char *pkl2;
@@ -1583,12 +1607,12 @@ M END\n",
10 12 1 0\n\
6 12 1 6\n\
2 13 1 0\n\
13 14 2 0\n\
14 15 1 0\n\
15 16 2 0\n\
16 17 1 0\n\
17 18 2 0\n\
13 18 1 0\n\
13 14 1 0\n\
14 15 2 0\n\
15 16 1 0\n\
16 17 2 0\n\
17 18 1 0\n\
13 18 2 0\n\
17 19 1 0\n\
19 20 1 0\n\
20 21 1 0\n\
@@ -1681,7 +1705,7 @@ M END\n",
v2[1] = xy25[1] - xy26[1];
double v1v2Theta = angle_deg_between_vectors(v1, v2);
assert(v1v2Theta > 10.0 && v1v2Theta < 15.0);
assert(strstr(molblock, inverted_wedges));
assert(molblock_has_wedge_lines(molblock, inverted_wedges));
free(mpkl_copy);
free(molblock);
free(svg);
@@ -1708,7 +1732,7 @@ M END\n",
v2[1] = xy25[1] - xy26[1];
v1v2Theta = angle_deg_between_vectors(v1, v2);
assert(v1v2Theta > 105.0 && v1v2Theta < 110.0);
assert(strstr(molblock, inverted_wedges));
assert(molblock_has_wedge_lines(molblock, inverted_wedges));
free(mpkl_copy);
free(molblock);
free(svg);
@@ -1737,7 +1761,7 @@ M END\n",
v2[1] = xy25[1] - xy26[1];
v1v2Theta = angle_deg_between_vectors(v1, v2);
assert(v1v2Theta > 145.0 && v1v2Theta < 150.0);
assert(!strstr(molblock, inverted_wedges));
assert(!molblock_has_wedge_lines(molblock, inverted_wedges));
free(mpkl_copy);
free(molblock);
free(svg);
@@ -1813,7 +1837,7 @@ M END\n",
v2[1] = xy25[1] - xy26[1];
double v1v2Theta = angle_deg_between_vectors(v1, v2);
assert(v1v2Theta > 10.0 && v1v2Theta < 15.0);
assert(strstr(molblock, inverted_wedges));
assert(molblock_has_wedge_lines(molblock, inverted_wedges));
free(mpkl_copy);
free(molblock);
free(svg);
@@ -1839,7 +1863,7 @@ M END\n",
v2[1] = xy25[1] - xy26[1];
v1v2Theta = angle_deg_between_vectors(v1, v2);
assert(v1v2Theta > 105.0 && v1v2Theta < 110.0);
assert(!strstr(molblock, inverted_wedges));
assert(!molblock_has_wedge_lines(molblock, inverted_wedges));
free(mpkl_copy);
free(molblock);
free(svg);
@@ -1868,7 +1892,7 @@ M END\n",
v2[1] = xy25[1] - xy26[1];
v1v2Theta = angle_deg_between_vectors(v1, v2);
assert(v1v2Theta > 145.0 && v1v2Theta < 150.0);
assert(!strstr(molblock, inverted_wedges));
assert(!molblock_has_wedge_lines(molblock, inverted_wedges));
free(mpkl_copy);
free(molblock);
free(svg);

View File

@@ -719,12 +719,12 @@ M END
-3.4910 1.0942 0.0000 F 0 0 0 0 0 0 0 0 0 0 0 0
1.7051 1.0942 0.0000 Cl 0 0 0 0 0 0 0 0 0 0 0 0
-3.4910 -1.9059 0.0000 Br 0 0 0 0 0 0 0 0 0 0 0 0
1 2 2 0
2 3 1 0
3 4 2 0
4 5 1 0
5 6 2 0
6 1 1 0
1 2 1 0
2 3 2 0
3 4 1 0
4 5 2 0
5 6 1 0
6 1 2 0
6 8 1 0
3 9 1 0
2 7 1 0
@@ -1556,12 +1556,12 @@ M END
10 12 1 0
6 12 1 6
2 13 1 0
13 14 2 0
14 15 1 0
15 16 2 0
16 17 1 0
17 18 2 0
13 18 1 0
13 14 1 0
14 15 2 0
15 16 1 0
16 17 2 0
17 18 1 0
13 18 2 0
17 19 1 0
19 20 1 0
20 21 1 0