molbuilder 1.0.0__py3-none-any.whl → 1.1.0__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.
@@ -0,0 +1,552 @@
1
+ """Reaction mechanism data model and predefined templates.
2
+
3
+ Provides an enumeration of mechanism types, a dataclass hierarchy for
4
+ describing mechanism stages with electron flows, and factory functions
5
+ for common organic reaction mechanisms (SN2, E2, radical substitution,
6
+ nucleophilic addition).
7
+
8
+ Each mechanism is decomposed into sequential stages, where each stage
9
+ specifies target distances, bond order changes, angle targets, and
10
+ electron flow arrows for visualization.
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from dataclasses import dataclass, field
16
+ from enum import Enum, auto
17
+
18
+
19
+ # ===================================================================
20
+ # Enumerations
21
+ # ===================================================================
22
+
23
+ class MechanismType(Enum):
24
+ """Classification of reaction mechanism types."""
25
+ SN2 = auto()
26
+ SN1 = auto()
27
+ E1 = auto()
28
+ E2 = auto()
29
+ ELECTROPHILIC_ADDITION = auto()
30
+ NUCLEOPHILIC_ADDITION = auto()
31
+ RADICAL_SUBSTITUTION = auto()
32
+ CONCERTED_PERICYCLIC = auto()
33
+
34
+
35
+ class FlowType(Enum):
36
+ """Type of electron flow arrow."""
37
+ CURLY_ARROW = auto() # Two-electron (heterolytic)
38
+ FISHHOOK_ARROW = auto() # One-electron (homolytic / radical)
39
+
40
+
41
+ # ===================================================================
42
+ # Data classes
43
+ # ===================================================================
44
+
45
+ @dataclass
46
+ class ElectronFlow:
47
+ """An electron flow arrow for mechanism visualization.
48
+
49
+ Attributes
50
+ ----------
51
+ from_atom : int
52
+ Atom index where the electron pair originates (lone pair or
53
+ bond).
54
+ to_bond : tuple[int, int]
55
+ Atom pair where electrons flow to (new bond being formed).
56
+ flow_type : FlowType
57
+ CURLY_ARROW for 2-electron, FISHHOOK_ARROW for 1-electron.
58
+ label : str
59
+ Optional annotation for the arrow.
60
+ """
61
+ from_atom: int
62
+ to_bond: tuple[int, int]
63
+ flow_type: FlowType = FlowType.CURLY_ARROW
64
+ label: str = ""
65
+
66
+
67
+ @dataclass
68
+ class MechanismStage:
69
+ """A single stage of a reaction mechanism.
70
+
71
+ Attributes
72
+ ----------
73
+ name : str
74
+ Human-readable name (e.g. "Backside attack", "Leaving group
75
+ departure").
76
+ distance_targets : dict[tuple[int, int], float]
77
+ Target interatomic distances in Angstroms for this stage.
78
+ Keys are (atom_i, atom_j) pairs.
79
+ bond_order_changes : dict[tuple[int, int], float]
80
+ Target fractional bond orders at the end of this stage.
81
+ angle_targets : dict[tuple[int, int, int], float]
82
+ Target bond angles in degrees (i-j-k triples).
83
+ electron_flows : list[ElectronFlow]
84
+ Electron flow arrows active during this stage.
85
+ duration_weight : float
86
+ Relative duration of this stage (for timing). Stages with
87
+ higher weight take proportionally longer in the animation.
88
+ annotation : str
89
+ Text annotation displayed during this stage.
90
+ """
91
+ name: str
92
+ distance_targets: dict[tuple[int, int], float] = field(default_factory=dict)
93
+ bond_order_changes: dict[tuple[int, int], float] = field(default_factory=dict)
94
+ angle_targets: dict[tuple[int, int, int], float] = field(default_factory=dict)
95
+ electron_flows: list[ElectronFlow] = field(default_factory=list)
96
+ duration_weight: float = 1.0
97
+ annotation: str = ""
98
+
99
+
100
+ @dataclass
101
+ class ReactionMechanism:
102
+ """A complete reaction mechanism composed of sequential stages.
103
+
104
+ Attributes
105
+ ----------
106
+ name : str
107
+ Mechanism name (e.g. "SN2 of CH3Cl + OH-").
108
+ mechanism_type : MechanismType
109
+ Classification of the mechanism.
110
+ stages : list[MechanismStage]
111
+ Ordered list of stages.
112
+ atom_roles : dict[str, int]
113
+ Named roles mapped to atom indices (e.g. "nucleophile" -> 5,
114
+ "leaving_group" -> 3).
115
+ """
116
+ name: str
117
+ mechanism_type: MechanismType
118
+ stages: list[MechanismStage] = field(default_factory=list)
119
+ atom_roles: dict[str, int] = field(default_factory=dict)
120
+
121
+
122
+ # ===================================================================
123
+ # Predefined mechanism template factories
124
+ # ===================================================================
125
+
126
+ def sn2_mechanism(substrate_C: int,
127
+ nucleophile: int,
128
+ leaving_group: int,
129
+ nuc_approach_dist: float = 3.5,
130
+ nuc_bond_dist: float = 1.5,
131
+ lg_depart_dist: float = 3.5) -> ReactionMechanism:
132
+ """Create an SN2 mechanism template.
133
+
134
+ The SN2 mechanism proceeds in a single concerted step:
135
+ nucleophile attacks the electrophilic carbon from the backside
136
+ while the leaving group departs, with inversion of configuration.
137
+
138
+ Parameters
139
+ ----------
140
+ substrate_C : int
141
+ Index of the electrophilic carbon.
142
+ nucleophile : int
143
+ Index of the nucleophile atom.
144
+ leaving_group : int
145
+ Index of the leaving group atom.
146
+ nuc_approach_dist : float
147
+ Initial Nu...C distance to start from.
148
+ nuc_bond_dist : float
149
+ Final Nu-C bond distance.
150
+ lg_depart_dist : float
151
+ Final C...LG distance.
152
+
153
+ Returns
154
+ -------
155
+ ReactionMechanism
156
+ """
157
+ nuc_key = (min(nucleophile, substrate_C), max(nucleophile, substrate_C))
158
+ lg_key = (min(leaving_group, substrate_C), max(leaving_group, substrate_C))
159
+
160
+ stages = [
161
+ # Stage 0: Approach
162
+ MechanismStage(
163
+ name="Nucleophile approach",
164
+ distance_targets={
165
+ (nucleophile, substrate_C): nuc_approach_dist * 0.7,
166
+ },
167
+ bond_order_changes={
168
+ nuc_key: 0.2,
169
+ lg_key: 0.9,
170
+ },
171
+ electron_flows=[
172
+ ElectronFlow(
173
+ from_atom=nucleophile,
174
+ to_bond=(nucleophile, substrate_C),
175
+ flow_type=FlowType.CURLY_ARROW,
176
+ label="Nu: attacks C",
177
+ ),
178
+ ],
179
+ duration_weight=1.0,
180
+ annotation="Nucleophile approaches electrophilic carbon",
181
+ ),
182
+ # Stage 1: Transition state
183
+ MechanismStage(
184
+ name="Transition state [Nu...C...LG]",
185
+ distance_targets={
186
+ (nucleophile, substrate_C): (nuc_bond_dist + nuc_approach_dist * 0.7) / 2,
187
+ (leaving_group, substrate_C): (nuc_bond_dist + lg_depart_dist) / 2,
188
+ },
189
+ bond_order_changes={
190
+ nuc_key: 0.5,
191
+ lg_key: 0.5,
192
+ },
193
+ electron_flows=[
194
+ ElectronFlow(
195
+ from_atom=nucleophile,
196
+ to_bond=(nucleophile, substrate_C),
197
+ flow_type=FlowType.CURLY_ARROW,
198
+ label="Partial bond forming",
199
+ ),
200
+ ElectronFlow(
201
+ from_atom=substrate_C,
202
+ to_bond=(substrate_C, leaving_group),
203
+ flow_type=FlowType.CURLY_ARROW,
204
+ label="Bond breaking",
205
+ ),
206
+ ],
207
+ duration_weight=1.5,
208
+ annotation="Transition state: pentacoordinate carbon",
209
+ ),
210
+ # Stage 2: Product formation
211
+ MechanismStage(
212
+ name="Product formation",
213
+ distance_targets={
214
+ (nucleophile, substrate_C): nuc_bond_dist,
215
+ (leaving_group, substrate_C): lg_depart_dist,
216
+ },
217
+ bond_order_changes={
218
+ nuc_key: 1.0,
219
+ lg_key: 0.0,
220
+ },
221
+ electron_flows=[
222
+ ElectronFlow(
223
+ from_atom=substrate_C,
224
+ to_bond=(substrate_C, leaving_group),
225
+ flow_type=FlowType.CURLY_ARROW,
226
+ label="LG departs with electrons",
227
+ ),
228
+ ],
229
+ duration_weight=1.0,
230
+ annotation="Leaving group departs; Walden inversion complete",
231
+ ),
232
+ ]
233
+
234
+ return ReactionMechanism(
235
+ name="SN2 mechanism",
236
+ mechanism_type=MechanismType.SN2,
237
+ stages=stages,
238
+ atom_roles={
239
+ "substrate_C": substrate_C,
240
+ "nucleophile": nucleophile,
241
+ "leaving_group": leaving_group,
242
+ },
243
+ )
244
+
245
+
246
+ def e2_mechanism(alpha_C: int,
247
+ beta_H: int,
248
+ base: int,
249
+ leaving_group: int,
250
+ base_approach_dist: float = 3.0,
251
+ base_H_dist: float = 1.0,
252
+ lg_depart_dist: float = 3.5) -> ReactionMechanism:
253
+ """Create an E2 elimination mechanism template.
254
+
255
+ The E2 mechanism is concerted: the base abstracts the beta-hydrogen
256
+ while the leaving group departs, forming a double bond.
257
+
258
+ Parameters
259
+ ----------
260
+ alpha_C : int
261
+ Index of the alpha carbon (bearing the leaving group).
262
+ beta_H : int
263
+ Index of the beta hydrogen.
264
+ base : int
265
+ Index of the base atom.
266
+ leaving_group : int
267
+ Index of the leaving group atom.
268
+
269
+ Returns
270
+ -------
271
+ ReactionMechanism
272
+ """
273
+ base_H_key = (min(base, beta_H), max(base, beta_H))
274
+ lg_key = (min(leaving_group, alpha_C), max(leaving_group, alpha_C))
275
+
276
+ stages = [
277
+ MechanismStage(
278
+ name="Base approach",
279
+ distance_targets={
280
+ (base, beta_H): base_approach_dist * 0.6,
281
+ },
282
+ bond_order_changes={
283
+ base_H_key: 0.3,
284
+ lg_key: 0.9,
285
+ },
286
+ electron_flows=[
287
+ ElectronFlow(
288
+ from_atom=base,
289
+ to_bond=(base, beta_H),
290
+ flow_type=FlowType.CURLY_ARROW,
291
+ label="Base abstracts H",
292
+ ),
293
+ ],
294
+ duration_weight=1.0,
295
+ annotation="Base approaches beta-hydrogen",
296
+ ),
297
+ MechanismStage(
298
+ name="Concerted E2 transition state",
299
+ distance_targets={
300
+ (base, beta_H): base_H_dist * 1.3,
301
+ (leaving_group, alpha_C): lg_depart_dist * 0.5,
302
+ },
303
+ bond_order_changes={
304
+ base_H_key: 0.7,
305
+ lg_key: 0.3,
306
+ },
307
+ electron_flows=[
308
+ ElectronFlow(
309
+ from_atom=base,
310
+ to_bond=(base, beta_H),
311
+ flow_type=FlowType.CURLY_ARROW,
312
+ label="H abstraction",
313
+ ),
314
+ ElectronFlow(
315
+ from_atom=alpha_C,
316
+ to_bond=(alpha_C, leaving_group),
317
+ flow_type=FlowType.CURLY_ARROW,
318
+ label="LG departure",
319
+ ),
320
+ ],
321
+ duration_weight=1.5,
322
+ annotation="Transition state: anti-periplanar elimination",
323
+ ),
324
+ MechanismStage(
325
+ name="Alkene product",
326
+ distance_targets={
327
+ (base, beta_H): base_H_dist,
328
+ (leaving_group, alpha_C): lg_depart_dist,
329
+ },
330
+ bond_order_changes={
331
+ base_H_key: 1.0,
332
+ lg_key: 0.0,
333
+ },
334
+ electron_flows=[],
335
+ duration_weight=1.0,
336
+ annotation="Double bond formed; leaving group departed",
337
+ ),
338
+ ]
339
+
340
+ return ReactionMechanism(
341
+ name="E2 elimination",
342
+ mechanism_type=MechanismType.E2,
343
+ stages=stages,
344
+ atom_roles={
345
+ "alpha_C": alpha_C,
346
+ "beta_H": beta_H,
347
+ "base": base,
348
+ "leaving_group": leaving_group,
349
+ },
350
+ )
351
+
352
+
353
+ def radical_substitution_mechanism(
354
+ target_C: int,
355
+ target_H: int,
356
+ radical: int,
357
+ rad_approach_dist: float = 3.0,
358
+ rad_H_dist: float = 1.1,
359
+ new_bond_dist: float = 1.5) -> ReactionMechanism:
360
+ """Create a radical substitution (propagation) mechanism.
361
+
362
+ Models the hydrogen abstraction step in radical chain reactions:
363
+ R* + H-C -> R-H + C*
364
+
365
+ Parameters
366
+ ----------
367
+ target_C : int
368
+ Index of the carbon losing the hydrogen.
369
+ target_H : int
370
+ Index of the hydrogen being abstracted.
371
+ radical : int
372
+ Index of the radical atom.
373
+
374
+ Returns
375
+ -------
376
+ ReactionMechanism
377
+ """
378
+ rad_H_key = (min(radical, target_H), max(radical, target_H))
379
+ ch_key = (min(target_C, target_H), max(target_C, target_H))
380
+
381
+ stages = [
382
+ MechanismStage(
383
+ name="Radical approach",
384
+ distance_targets={
385
+ (radical, target_H): rad_approach_dist * 0.6,
386
+ },
387
+ bond_order_changes={
388
+ rad_H_key: 0.2,
389
+ ch_key: 0.9,
390
+ },
391
+ electron_flows=[
392
+ ElectronFlow(
393
+ from_atom=radical,
394
+ to_bond=(radical, target_H),
395
+ flow_type=FlowType.FISHHOOK_ARROW,
396
+ label="Radical attacks H",
397
+ ),
398
+ ],
399
+ duration_weight=1.0,
400
+ annotation="Radical approaches C-H bond",
401
+ ),
402
+ MechanismStage(
403
+ name="Transition state",
404
+ distance_targets={
405
+ (radical, target_H): (rad_H_dist + rad_approach_dist * 0.6) / 2,
406
+ },
407
+ bond_order_changes={
408
+ rad_H_key: 0.5,
409
+ ch_key: 0.5,
410
+ },
411
+ electron_flows=[
412
+ ElectronFlow(
413
+ from_atom=radical,
414
+ to_bond=(radical, target_H),
415
+ flow_type=FlowType.FISHHOOK_ARROW,
416
+ label="1e- transfer",
417
+ ),
418
+ ElectronFlow(
419
+ from_atom=target_C,
420
+ to_bond=(target_C, target_H),
421
+ flow_type=FlowType.FISHHOOK_ARROW,
422
+ label="1e- transfer",
423
+ ),
424
+ ],
425
+ duration_weight=1.5,
426
+ annotation="Transition state: R...H...C",
427
+ ),
428
+ MechanismStage(
429
+ name="Product (new radical)",
430
+ distance_targets={
431
+ (radical, target_H): rad_H_dist,
432
+ },
433
+ bond_order_changes={
434
+ rad_H_key: 1.0,
435
+ ch_key: 0.0,
436
+ },
437
+ electron_flows=[],
438
+ duration_weight=1.0,
439
+ annotation="H transferred; carbon radical formed",
440
+ ),
441
+ ]
442
+
443
+ return ReactionMechanism(
444
+ name="Radical substitution (propagation)",
445
+ mechanism_type=MechanismType.RADICAL_SUBSTITUTION,
446
+ stages=stages,
447
+ atom_roles={
448
+ "target_C": target_C,
449
+ "target_H": target_H,
450
+ "radical": radical,
451
+ },
452
+ )
453
+
454
+
455
+ def nucleophilic_addition_mechanism(
456
+ carbonyl_C: int,
457
+ carbonyl_O: int,
458
+ nucleophile: int,
459
+ nuc_approach_dist: float = 3.5,
460
+ nuc_bond_dist: float = 1.5) -> ReactionMechanism:
461
+ """Create a nucleophilic addition to carbonyl mechanism.
462
+
463
+ Nucleophile attacks the carbonyl carbon, converting C=O to C-O^-.
464
+
465
+ Parameters
466
+ ----------
467
+ carbonyl_C : int
468
+ Index of the carbonyl carbon.
469
+ carbonyl_O : int
470
+ Index of the carbonyl oxygen.
471
+ nucleophile : int
472
+ Index of the nucleophile atom.
473
+
474
+ Returns
475
+ -------
476
+ ReactionMechanism
477
+ """
478
+ nuc_C_key = (min(nucleophile, carbonyl_C), max(nucleophile, carbonyl_C))
479
+ co_key = (min(carbonyl_C, carbonyl_O), max(carbonyl_C, carbonyl_O))
480
+
481
+ stages = [
482
+ MechanismStage(
483
+ name="Nucleophile approach",
484
+ distance_targets={
485
+ (nucleophile, carbonyl_C): nuc_approach_dist * 0.6,
486
+ },
487
+ bond_order_changes={
488
+ nuc_C_key: 0.2,
489
+ co_key: 1.8,
490
+ },
491
+ electron_flows=[
492
+ ElectronFlow(
493
+ from_atom=nucleophile,
494
+ to_bond=(nucleophile, carbonyl_C),
495
+ flow_type=FlowType.CURLY_ARROW,
496
+ label="Nu: attacks C=O",
497
+ ),
498
+ ],
499
+ duration_weight=1.0,
500
+ annotation="Nucleophile approaches electrophilic carbonyl C",
501
+ ),
502
+ MechanismStage(
503
+ name="Tetrahedral intermediate forming",
504
+ distance_targets={
505
+ (nucleophile, carbonyl_C): (nuc_bond_dist + nuc_approach_dist * 0.6) / 2,
506
+ },
507
+ bond_order_changes={
508
+ nuc_C_key: 0.6,
509
+ co_key: 1.4,
510
+ },
511
+ electron_flows=[
512
+ ElectronFlow(
513
+ from_atom=nucleophile,
514
+ to_bond=(nucleophile, carbonyl_C),
515
+ flow_type=FlowType.CURLY_ARROW,
516
+ label="Bond forming",
517
+ ),
518
+ ElectronFlow(
519
+ from_atom=carbonyl_C,
520
+ to_bond=(carbonyl_C, carbonyl_O),
521
+ flow_type=FlowType.CURLY_ARROW,
522
+ label="Pi electrons to O",
523
+ ),
524
+ ],
525
+ duration_weight=1.5,
526
+ annotation="Rehybridization: sp2 to sp3",
527
+ ),
528
+ MechanismStage(
529
+ name="Tetrahedral product",
530
+ distance_targets={
531
+ (nucleophile, carbonyl_C): nuc_bond_dist,
532
+ },
533
+ bond_order_changes={
534
+ nuc_C_key: 1.0,
535
+ co_key: 1.0,
536
+ },
537
+ electron_flows=[],
538
+ duration_weight=1.0,
539
+ annotation="Tetrahedral alkoxide intermediate formed",
540
+ ),
541
+ ]
542
+
543
+ return ReactionMechanism(
544
+ name="Nucleophilic addition to carbonyl",
545
+ mechanism_type=MechanismType.NUCLEOPHILIC_ADDITION,
546
+ stages=stages,
547
+ atom_roles={
548
+ "carbonyl_C": carbonyl_C,
549
+ "carbonyl_O": carbonyl_O,
550
+ "nucleophile": nucleophile,
551
+ },
552
+ )