synkit 1.4.2b1__py3-none-any.whl → 2.0.0.dev1__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.
- synkit/Chem/Fingerprint/transformation_fp.py +8 -4
- synkit/Chem/Molecule/atom_features.py +1 -0
- synkit/Chem/Reaction/__init__.py +15 -1
- synkit/Chem/Reaction/explicit_h_audit.py +237 -0
- synkit/Chem/Reaction/tautomerize.py +1 -4
- synkit/Data/Mech/README.md +46 -0
- synkit/Data/Mech/polar.json +15921 -0
- synkit/Data/Mech/radical.json +12062 -0
- synkit/Data/Mech/stereo.json +6306 -0
- synkit/Data/aldol.json.gz +1 -0
- synkit/Data/paracetamol.json.gz +1 -0
- synkit/Graph/Hyrogen/_misc.py +7 -1
- synkit/Graph/ITS/its_construction.py +6 -0
- synkit/Graph/ITS/its_expand.py +115 -46
- synkit/Graph/ITS/its_reverter.py +28 -0
- synkit/Graph/ITS/rc_extractor.py +9 -0
- synkit/Graph/Matcher/batch_cluster.py +12 -39
- synkit/Graph/Matcher/graph_cluster.py +42 -52
- synkit/Graph/Matcher/graph_matcher.py +5 -48
- synkit/Graph/Matcher/subgraph_matcher.py +37 -94
- synkit/Graph/Mech/conversion.py +162 -0
- synkit/Graph/Mech/electron_accounting.py +28 -137
- synkit/Graph/Stereo/__init__.py +108 -0
- synkit/Graph/Stereo/changes.py +177 -0
- synkit/Graph/Stereo/couplings.py +224 -0
- synkit/Graph/Stereo/descriptors.py +734 -0
- synkit/Graph/Stereo/identity.py +565 -0
- synkit/Graph/Stereo/interchange.py +496 -0
- synkit/Graph/Stereo/matching.py +436 -0
- synkit/Graph/Stereo/outcomes.py +99 -0
- synkit/Graph/Stereo/rdkit_adapter.py +638 -0
- synkit/Graph/Wildcard/its_merge.py +6 -6
- synkit/Graph/syn_graph.py +48 -6
- synkit/Graph/utils.py +131 -2
- synkit/IO/__init__.py +1 -0
- synkit/IO/chem_converter.py +30 -23
- synkit/IO/conversion.py +42 -0
- synkit/IO/data_io.py +0 -45
- synkit/IO/graph_to_mol.py +69 -4
- synkit/IO/mol_to_graph.py +73 -1
- synkit/Mechanism/__init__.py +134 -0
- synkit/Mechanism/adapters.py +136 -0
- synkit/Mechanism/audit.py +323 -0
- synkit/Mechanism/benchmark.py +618 -0
- synkit/Mechanism/compatibility.py +10 -0
- synkit/Mechanism/drawing.py +85 -0
- synkit/Mechanism/equivalence.py +443 -0
- synkit/Mechanism/evidence.py +255 -0
- synkit/Mechanism/interchange.py +124 -0
- synkit/Mechanism/model.py +1214 -0
- synkit/Mechanism/radical_data.py +238 -0
- synkit/Mechanism/replay.py +574 -0
- synkit/Mechanism/schema.py +206 -0
- synkit/Mechanism/stereo_state.py +348 -0
- synkit/Mechanism/symbols.py +91 -0
- synkit/Rule/Compose/_identity.py +43 -0
- synkit/Rule/stereo_identity.py +200 -0
- synkit/Rule/syn_rule.py +474 -3
- synkit/Synthesis/Reactor/batch_reactor.py +11 -36
- synkit/Synthesis/Reactor/benchmark.py +0 -4
- synkit/Synthesis/Reactor/rbl_engine.py +258 -859
- synkit/Synthesis/Reactor/rule_filter.py +3 -5
- synkit/Synthesis/Reactor/syn_reactor.py +1748 -81
- synkit/Synthesis/reactor_utils.py +0 -27
- synkit/Vis/epd/models.py +30 -1
- synkit/Vis/epd/render.py +29 -14
- synkit/Vis/epd/visualizer.py +27 -13
- synkit/Vis/its_drawer.py +615 -0
- synkit/Vis/molecule_drawer.py +565 -0
- synkit/Vis/mtg_drawer.py +769 -0
- synkit/Vis/reaction/rule.py +11 -49
- synkit/Vis/reaction_drawer.py +285 -0
- synkit/Vis/vis_synedu/Vis/__init__.py +7 -0
- synkit/Vis/vis_synedu/Vis/dpo.py +862 -0
- synkit/Vis/vis_synedu/rxn_vis.py +382 -0
- synkit/Vis/vis_synedu/vis.py +501 -0
- synkit/version.py +12 -3
- {synkit-1.4.2b1.dist-info → synkit-2.0.0.dev1.dist-info}/METADATA +66 -5
- {synkit-1.4.2b1.dist-info → synkit-2.0.0.dev1.dist-info}/RECORD +82 -54
- {synkit-1.4.2b1.dist-info → synkit-2.0.0.dev1.dist-info}/WHEEL +1 -1
- synkit-2.0.0.dev1.dist-info/licenses/LICENSES/StereoMolGraph-MIT.txt +30 -0
- synkit/CRN/Construct/DAG/crn.py +0 -207
- synkit/CRN/Construct/DAG/mod_crn.py +0 -160
- synkit/IO/dg_to_gml.py +0 -133
- synkit/Rule/Apply/reactor_rule.py +0 -90
- synkit/Rule/Apply/retro_reactor.py +0 -212
- synkit/Rule/Apply/rule_rbl.py +0 -86
- synkit/Rule/Compose/compose_rule.py +0 -225
- synkit/Rule/Compose/rule_compose.py +0 -236
- synkit/Rule/Compose/rule_mapping.py +0 -315
- synkit/Rule/Compose/seq_comp.py +0 -71
- synkit/Rule/Compose/valence_constrain.py +0 -107
- synkit/Synthesis/MSR/multi_steps.py +0 -137
- synkit/Synthesis/Reactor/mod_aam.py +0 -279
- synkit/Synthesis/Reactor/mod_reactor.py +0 -427
- synkit/Synthesis/Reactor/single_predictor.py +0 -90
- {synkit-1.4.2b1.dist-info → synkit-2.0.0.dev1.dist-info}/licenses/LICENSE +0 -0
|
@@ -36,14 +36,18 @@ class TransformationFP:
|
|
|
36
36
|
|
|
37
37
|
@staticmethod
|
|
38
38
|
def convert_arr2vec(arr: np.ndarray) -> cDataStructs.ExplicitBitVect:
|
|
39
|
-
"""Convert a NumPy array
|
|
39
|
+
"""Convert a non-negative NumPy array into an RDKit bit vector.
|
|
40
40
|
|
|
41
|
-
:param arr: Array of
|
|
41
|
+
:param arr: Array of non-negative fingerprint values.
|
|
42
42
|
:type arr: np.ndarray
|
|
43
|
-
:returns: RDKit bit vector
|
|
43
|
+
:returns: RDKit bit vector with positive values encoded as 1.
|
|
44
44
|
:rtype: cDataStructs.ExplicitBitVect
|
|
45
|
+
:raises ValueError: If the array contains negative or non-finite values.
|
|
45
46
|
"""
|
|
46
|
-
|
|
47
|
+
flat = np.asarray(arr).flatten()
|
|
48
|
+
if not np.isfinite(flat).all() or (flat < 0).any():
|
|
49
|
+
raise ValueError("Bit-vector output requires non-negative finite values.")
|
|
50
|
+
bitstr = "".join("1" if x else "0" for x in flat)
|
|
47
51
|
return cDataStructs.CreateFromBitString(bitstr)
|
|
48
52
|
|
|
49
53
|
def fit(
|
synkit/Chem/Reaction/__init__.py
CHANGED
|
@@ -1,6 +1,20 @@
|
|
|
1
1
|
from .aam_validator import AAMValidator
|
|
2
|
+
from .explicit_h_audit import (
|
|
3
|
+
ExplicitHydrogenReactionAudit,
|
|
4
|
+
MappedBondChange,
|
|
5
|
+
audit_explicit_h_reaction,
|
|
6
|
+
reaction_smiles_from_annotated_text,
|
|
7
|
+
)
|
|
8
|
+
from synkit.Chem.utils import remove_explicit_H_from_rsmi
|
|
2
9
|
|
|
3
10
|
# from .standardize import Standardize
|
|
4
11
|
# from .canon_rsmi import CanonRSMI
|
|
5
12
|
|
|
6
|
-
__all__ = [
|
|
13
|
+
__all__ = [
|
|
14
|
+
"AAMValidator",
|
|
15
|
+
"ExplicitHydrogenReactionAudit",
|
|
16
|
+
"MappedBondChange",
|
|
17
|
+
"audit_explicit_h_reaction",
|
|
18
|
+
"reaction_smiles_from_annotated_text",
|
|
19
|
+
"remove_explicit_H_from_rsmi",
|
|
20
|
+
]
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
"""Selection gate for mapped reactions with hydrogen in the reaction center.
|
|
2
|
+
|
|
3
|
+
This module deliberately uses a narrow definition suitable for regression and
|
|
4
|
+
conformance data. An accepted reaction must be fully atom mapped, conserve
|
|
5
|
+
mapped atom identities, molecular formula, and total formal charge, and contain
|
|
6
|
+
at least one explicit mapped hydrogen incident to a changed bond.
|
|
7
|
+
|
|
8
|
+
The check is stricter than merely searching for ``"[H:"``. A mapped hydrogen
|
|
9
|
+
which is present but remote from every bond change does not qualify.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from collections import Counter
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
|
|
17
|
+
from rdkit import Chem
|
|
18
|
+
from rdkit.Chem.rdMolDescriptors import CalcMolFormula
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(frozen=True, order=True)
|
|
22
|
+
class MappedBondChange:
|
|
23
|
+
"""One mapped bond before and after a reaction."""
|
|
24
|
+
|
|
25
|
+
atom_maps: tuple[int, int]
|
|
26
|
+
before: float
|
|
27
|
+
after: float
|
|
28
|
+
|
|
29
|
+
@property
|
|
30
|
+
def kind(self) -> str:
|
|
31
|
+
if self.before == 0.0:
|
|
32
|
+
return "FORMED"
|
|
33
|
+
if self.after == 0.0:
|
|
34
|
+
return "BROKEN"
|
|
35
|
+
return "ORDER_CHANGED"
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass(frozen=True)
|
|
39
|
+
class ExplicitHydrogenReactionAudit:
|
|
40
|
+
"""Structured result returned by :func:`audit_explicit_h_reaction`."""
|
|
41
|
+
|
|
42
|
+
reaction: str
|
|
43
|
+
errors: tuple[str, ...] = ()
|
|
44
|
+
reactant_formula: str | None = None
|
|
45
|
+
product_formula: str | None = None
|
|
46
|
+
reactant_charge: int | None = None
|
|
47
|
+
product_charge: int | None = None
|
|
48
|
+
atom_maps: tuple[int, ...] = ()
|
|
49
|
+
explicit_hydrogen_maps: tuple[int, ...] = ()
|
|
50
|
+
changed_hydrogen_maps: tuple[int, ...] = ()
|
|
51
|
+
reaction_center_maps: tuple[int, ...] = ()
|
|
52
|
+
changed_bonds: tuple[MappedBondChange, ...] = ()
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def accepted(self) -> bool:
|
|
56
|
+
"""Whether the reaction satisfies every strict selection criterion."""
|
|
57
|
+
|
|
58
|
+
return not self.errors
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass(frozen=True)
|
|
62
|
+
class _ReactionSide:
|
|
63
|
+
formula: str
|
|
64
|
+
elemental_composition: tuple[tuple[tuple[int, int], int], ...]
|
|
65
|
+
charge: int
|
|
66
|
+
atoms_by_map: dict[int, tuple[int, int]]
|
|
67
|
+
bonds_by_maps: dict[tuple[int, int], float]
|
|
68
|
+
unmapped_atoms: tuple[int, ...]
|
|
69
|
+
duplicate_maps: tuple[int, ...]
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def reaction_smiles_from_annotated_text(text: str) -> str:
|
|
73
|
+
"""Return the reaction token from legacy ``SMIRKS arrow-code`` text.
|
|
74
|
+
|
|
75
|
+
SynKit's polar and radical CSV files append electron-flow notation after a
|
|
76
|
+
space. Ordinary reaction SMILES contains no whitespace, so the first token
|
|
77
|
+
is the complete reaction in both the annotated and unannotated forms.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
stripped = str(text).strip()
|
|
81
|
+
return stripped.split(maxsplit=1)[0] if stripped else ""
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _parse_side(smiles: str) -> _ReactionSide | None:
|
|
85
|
+
parser = Chem.SmilesParserParams()
|
|
86
|
+
parser.removeHs = False
|
|
87
|
+
molecule = Chem.MolFromSmiles(smiles, parser)
|
|
88
|
+
if molecule is None:
|
|
89
|
+
return None
|
|
90
|
+
|
|
91
|
+
atoms_by_map: dict[int, tuple[int, int]] = {}
|
|
92
|
+
unmapped_atoms = []
|
|
93
|
+
duplicate_maps = []
|
|
94
|
+
for atom in molecule.GetAtoms():
|
|
95
|
+
atom_map = int(atom.GetAtomMapNum())
|
|
96
|
+
if atom_map <= 0:
|
|
97
|
+
unmapped_atoms.append(atom.GetIdx())
|
|
98
|
+
continue
|
|
99
|
+
if atom_map in atoms_by_map:
|
|
100
|
+
duplicate_maps.append(atom_map)
|
|
101
|
+
continue
|
|
102
|
+
# Charge, radical state, and hydrogen count may legitimately change.
|
|
103
|
+
# Element and isotope may not change under an atom mapping.
|
|
104
|
+
atoms_by_map[atom_map] = (atom.GetAtomicNum(), atom.GetIsotope())
|
|
105
|
+
|
|
106
|
+
bonds_by_maps = {}
|
|
107
|
+
for bond in molecule.GetBonds():
|
|
108
|
+
begin_map = int(bond.GetBeginAtom().GetAtomMapNum())
|
|
109
|
+
end_map = int(bond.GetEndAtom().GetAtomMapNum())
|
|
110
|
+
if begin_map <= 0 or end_map <= 0:
|
|
111
|
+
continue
|
|
112
|
+
key = tuple(sorted((begin_map, end_map)))
|
|
113
|
+
bonds_by_maps[key] = float(bond.GetBondTypeAsDouble())
|
|
114
|
+
|
|
115
|
+
# CalcMolFormula includes a charge suffix (for example ``H4N+``). Keep
|
|
116
|
+
# that human-readable value in the report, but compare mass balance using
|
|
117
|
+
# an isotope-aware elemental inventory so charge has its own error code.
|
|
118
|
+
composition: Counter[tuple[int, int]] = Counter()
|
|
119
|
+
for atom in molecule.GetAtoms():
|
|
120
|
+
composition[(atom.GetAtomicNum(), atom.GetIsotope())] += 1
|
|
121
|
+
if atom.GetAtomicNum() != 1:
|
|
122
|
+
hydrogen_count = atom.GetTotalNumHs(includeNeighbors=False)
|
|
123
|
+
if hydrogen_count:
|
|
124
|
+
composition[(1, 0)] += int(hydrogen_count)
|
|
125
|
+
|
|
126
|
+
return _ReactionSide(
|
|
127
|
+
formula=CalcMolFormula(molecule),
|
|
128
|
+
elemental_composition=tuple(sorted(composition.items())),
|
|
129
|
+
charge=int(Chem.GetFormalCharge(molecule)),
|
|
130
|
+
atoms_by_map=atoms_by_map,
|
|
131
|
+
bonds_by_maps=bonds_by_maps,
|
|
132
|
+
unmapped_atoms=tuple(unmapped_atoms),
|
|
133
|
+
duplicate_maps=tuple(sorted(set(duplicate_maps))),
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def audit_explicit_h_reaction(text: str) -> ExplicitHydrogenReactionAudit:
|
|
138
|
+
"""Audit a mapped reaction for strict explicit-H-center suitability.
|
|
139
|
+
|
|
140
|
+
Parameters
|
|
141
|
+
----------
|
|
142
|
+
text:
|
|
143
|
+
A mapped reaction SMILES, optionally followed by SynKit's legacy
|
|
144
|
+
whitespace-separated electron-flow annotation.
|
|
145
|
+
|
|
146
|
+
Returns
|
|
147
|
+
-------
|
|
148
|
+
ExplicitHydrogenReactionAudit
|
|
149
|
+
A report with stable error codes and the detected mapped bond changes.
|
|
150
|
+
"""
|
|
151
|
+
|
|
152
|
+
reaction = reaction_smiles_from_annotated_text(text)
|
|
153
|
+
if reaction.count(">>") != 1:
|
|
154
|
+
return ExplicitHydrogenReactionAudit(
|
|
155
|
+
reaction,
|
|
156
|
+
errors=("INVALID_REACTION_SEPARATOR",),
|
|
157
|
+
)
|
|
158
|
+
|
|
159
|
+
reactant_text, product_text = reaction.split(">>", 1)
|
|
160
|
+
reactants = _parse_side(reactant_text)
|
|
161
|
+
products = _parse_side(product_text)
|
|
162
|
+
if reactants is None or products is None:
|
|
163
|
+
errors = []
|
|
164
|
+
if reactants is None:
|
|
165
|
+
errors.append("REACTANT_PARSE_FAILED")
|
|
166
|
+
if products is None:
|
|
167
|
+
errors.append("PRODUCT_PARSE_FAILED")
|
|
168
|
+
return ExplicitHydrogenReactionAudit(reaction, errors=tuple(errors))
|
|
169
|
+
|
|
170
|
+
errors = []
|
|
171
|
+
if reactants.unmapped_atoms:
|
|
172
|
+
errors.append("UNMAPPED_REACTANT_ATOM")
|
|
173
|
+
if products.unmapped_atoms:
|
|
174
|
+
errors.append("UNMAPPED_PRODUCT_ATOM")
|
|
175
|
+
if reactants.duplicate_maps:
|
|
176
|
+
errors.append("DUPLICATE_REACTANT_MAP")
|
|
177
|
+
if products.duplicate_maps:
|
|
178
|
+
errors.append("DUPLICATE_PRODUCT_MAP")
|
|
179
|
+
|
|
180
|
+
reactant_maps = set(reactants.atoms_by_map)
|
|
181
|
+
product_maps = set(products.atoms_by_map)
|
|
182
|
+
if reactant_maps != product_maps:
|
|
183
|
+
errors.append("MAP_INVENTORY_MISMATCH")
|
|
184
|
+
elif reactants.atoms_by_map != products.atoms_by_map:
|
|
185
|
+
errors.append("MAPPED_ATOM_IDENTITY_MISMATCH")
|
|
186
|
+
|
|
187
|
+
if reactants.elemental_composition != products.elemental_composition:
|
|
188
|
+
errors.append("FORMULA_IMBALANCE")
|
|
189
|
+
if reactants.charge != products.charge:
|
|
190
|
+
errors.append("CHARGE_IMBALANCE")
|
|
191
|
+
|
|
192
|
+
changed_bonds = []
|
|
193
|
+
all_bonds = set(reactants.bonds_by_maps) | set(products.bonds_by_maps)
|
|
194
|
+
for atom_maps in sorted(all_bonds):
|
|
195
|
+
before = reactants.bonds_by_maps.get(atom_maps, 0.0)
|
|
196
|
+
after = products.bonds_by_maps.get(atom_maps, 0.0)
|
|
197
|
+
if before != after:
|
|
198
|
+
changed_bonds.append(MappedBondChange(atom_maps, before, after))
|
|
199
|
+
|
|
200
|
+
hydrogen_maps = tuple(
|
|
201
|
+
sorted(
|
|
202
|
+
atom_map
|
|
203
|
+
for atom_map, identity in reactants.atoms_by_map.items()
|
|
204
|
+
if identity[0] == 1
|
|
205
|
+
)
|
|
206
|
+
)
|
|
207
|
+
if not hydrogen_maps:
|
|
208
|
+
errors.append("NO_EXPLICIT_MAPPED_HYDROGEN")
|
|
209
|
+
|
|
210
|
+
center_maps = tuple(
|
|
211
|
+
sorted({atom_map for change in changed_bonds for atom_map in change.atom_maps})
|
|
212
|
+
)
|
|
213
|
+
changed_hydrogen_maps = tuple(sorted(set(hydrogen_maps) & set(center_maps)))
|
|
214
|
+
if not changed_hydrogen_maps:
|
|
215
|
+
errors.append("NO_CHANGED_EXPLICIT_HYDROGEN")
|
|
216
|
+
|
|
217
|
+
return ExplicitHydrogenReactionAudit(
|
|
218
|
+
reaction=reaction,
|
|
219
|
+
errors=tuple(errors),
|
|
220
|
+
reactant_formula=reactants.formula,
|
|
221
|
+
product_formula=products.formula,
|
|
222
|
+
reactant_charge=reactants.charge,
|
|
223
|
+
product_charge=products.charge,
|
|
224
|
+
atom_maps=tuple(sorted(reactant_maps & product_maps)),
|
|
225
|
+
explicit_hydrogen_maps=hydrogen_maps,
|
|
226
|
+
changed_hydrogen_maps=changed_hydrogen_maps,
|
|
227
|
+
reaction_center_maps=center_maps,
|
|
228
|
+
changed_bonds=tuple(changed_bonds),
|
|
229
|
+
)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
__all__ = [
|
|
233
|
+
"ExplicitHydrogenReactionAudit",
|
|
234
|
+
"MappedBondChange",
|
|
235
|
+
"audit_explicit_h_reaction",
|
|
236
|
+
"reaction_smiles_from_annotated_text",
|
|
237
|
+
]
|
|
@@ -121,10 +121,7 @@ class Tautomerize:
|
|
|
121
121
|
mol = Chem.MolFromSmiles(smiles)
|
|
122
122
|
if mol is None:
|
|
123
123
|
return []
|
|
124
|
-
|
|
125
|
-
graph, groups = smiles_to_graph_and_functional_groups(smiles)
|
|
126
|
-
except ValueError:
|
|
127
|
-
return []
|
|
124
|
+
graph, groups = smiles_to_graph_and_functional_groups(smiles)
|
|
128
125
|
node_to_idx = {
|
|
129
126
|
(
|
|
130
127
|
atom.GetAtomMapNum() if atom.GetAtomMapNum() else atom.GetIdx() + 1
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# MechanismBench
|
|
2
|
+
|
|
3
|
+
`Data/Mech/` is the canonical on-branch location for the three reviewed
|
|
4
|
+
MechanismBench partitions. The former large `polar.csv` and `radical.csv`
|
|
5
|
+
source pools are not vendored; all release cases and their review provenance
|
|
6
|
+
are embedded in the JSON manifests below.
|
|
7
|
+
|
|
8
|
+
The public, executable MechanismBench layout has exactly three partitions:
|
|
9
|
+
|
|
10
|
+
- `radical.json` — 80 chemistry-reviewed, macro-balanced radical mechanisms.
|
|
11
|
+
Every case embeds its correction history, strict forward/reverse replay, and
|
|
12
|
+
unmapped rule-reapplication evidence.
|
|
13
|
+
- `polar.json` — 80 reviewed two-electron mechanisms selected deterministically
|
|
14
|
+
from SynEPD's 1,915-record `polar.json` source pool. The manifest records the
|
|
15
|
+
source SHA-256, CC BY 4.0 provenance, eight top-level POLAR strata, and strict
|
|
16
|
+
replay/reapplication evidence for every selected record. The full source pool
|
|
17
|
+
is intentionally not copied here.
|
|
18
|
+
- `stereo.json` — 72 positive transformations: the original 40 reaction-SMILES
|
|
19
|
+
cases, four reviewed non-tetrahedral/atrop rewrite fixtures, seven Phase 2R
|
|
20
|
+
electron-flow × stereo fixtures, and 21 reviewed native-descriptor rewrites.
|
|
21
|
+
Its eight negative assertions are retained as corruption/specification
|
|
22
|
+
fixtures and do not count as positive cases.
|
|
23
|
+
|
|
24
|
+
The compact nine-case rewrite conformance suite is development-only data at
|
|
25
|
+
`Test/Synthesis/Reactor/fixtures/small_rewrite_conformance.json`. It deliberately
|
|
26
|
+
does not define a MechanismBench partition.
|
|
27
|
+
|
|
28
|
+
The current stereo baseline is 72 positives, not 80. Any future claim of an
|
|
29
|
+
80-positive stereo partition requires eight further independently reviewed
|
|
30
|
+
positive cases; negative fixtures must never be counted to fill that gap.
|
|
31
|
+
|
|
32
|
+
Raw candidate pools, audit summaries, and release-owner checklists do not belong
|
|
33
|
+
in this directory. They should be regenerated from source data or kept as project
|
|
34
|
+
documentation rather than presented as benchmark cases.
|
|
35
|
+
|
|
36
|
+
Generate the current replay/corruption/runtime evidence with:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
python tools/mechanismbench_evidence.py \
|
|
40
|
+
--output Data/Mech/evidence/mechanismbench_evidence.json
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The report currently covers typed `MechanismRecord` fixtures. It records the
|
|
44
|
+
reaction-SMILES, non-tetrahedral, and promoted native-descriptor rewrite
|
|
45
|
+
fixtures that remain outside that shared replay representation instead of
|
|
46
|
+
assigning them fabricated metrics.
|