smiles-js 1.1.0 → 2.0.0
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.
- package/API.md +43 -20
- package/README.md +1 -1
- package/docs/EXAMPLES.md +2 -2
- package/docs/IMPLEMENTATION_ROADMAP.md +3 -3
- package/docs/PARSER_REFACTOR_PLAN.md +18 -18
- package/docs/PRODUCTION_AUDIT.md +7 -7
- package/docs/TEST_DRIVE_RESULTS.md +2 -2
- package/docs/atorvastatin-named.js +9 -9
- package/docs/esomeprazole-showcase.js +1 -1
- package/docs/readable-generated-code/{READABLE_GENERATED_CODE.md → 10_READABLE_GENERATED_CODE.md} +6 -6
- package/docs/readable-generated-code/{AFTER_ACTION_REPORT.md → 40_AFTER_ACTION_REPORT.md} +4 -4
- package/docs/readable-generated-code/{UPDATED_PLAN.md → 50_UPDATED_PLAN.md} +2 -2
- package/docs/readable-generated-code/60_PLAN.md +155 -0
- package/docs/readable-generated-code/PLAN.md +24 -0
- package/docs/ritonavir-synthesis.js +3 -3
- package/docs/sildenafil-synthesis.js +3 -3
- package/examples/basic-usage.js +5 -5
- package/examples/decompiler-demo.js +1 -1
- package/examples/fused-ring-manipulation.js +6 -6
- package/examples/linear-manipulation.js +5 -5
- package/package.json +1 -1
- package/src/codegen/branch-crossing-ring.test.js +0 -3
- package/src/codegen/branch-walker.js +5 -0
- package/src/codegen/index.test.js +1 -1
- package/src/codegen/interleaved-fused-ring.test.js +0 -2
- package/src/codegen/simple-fused-ring.js +115 -3
- package/src/codegen/smiles-codegen-core.js +11 -7
- package/src/codegen/smiles-codegen-core.test.js +4 -4
- package/src/constructors.test.js +7 -7
- package/src/decompiler.js +510 -488
- package/src/decompiler.test.js +130 -195
- package/src/layout/index.js +122 -26
- package/src/layout/index.test.js +26 -8
- package/src/manipulation.js +230 -33
- package/src/manipulation.test.js +25 -25
- package/src/method-attachers.js +10 -10
- package/src/parser/ast-builder.js +16 -2
- package/src/parser/branch-utils.test.js +8 -0
- package/src/parser/ring-group-builder.js +129 -23
- package/src/parser/ring-node-builder.js +3 -0
- package/src/parser/ring-utils.test.js +5 -0
- package/src/parser/smiles-parser-core.test.js +1 -1
- package/src/roundtrip.test.js +9 -22
- package/src/sequential-rings.test.js +48 -16
- package/test-integration/__snapshots__/acetaminophen.test.js.snap +4 -4
- package/test-integration/__snapshots__/adjuvant-analgesics.test.js.snap +52 -62
- package/test-integration/__snapshots__/cholesterol-drugs.test.js.snap +103 -154
- package/test-integration/__snapshots__/dexamethasone.test.js.snap +43 -33
- package/test-integration/__snapshots__/endocannabinoids.test.js.snap +64 -61
- package/test-integration/__snapshots__/hypertension-medication.test.js.snap +35 -57
- package/test-integration/__snapshots__/local-anesthetics.test.js.snap +16 -16
- package/test-integration/__snapshots__/nsaids-otc.test.js.snap +19 -23
- package/test-integration/__snapshots__/nsaids-prescription.test.js.snap +51 -69
- package/test-integration/__snapshots__/opioids.test.js.snap +135 -136
- package/test-integration/__snapshots__/steroids.test.js.snap +432 -489
- package/test-integration/acetaminophen.smiles.js +2 -2
- package/test-integration/cortisone.test.js +5 -1
- package/test-integration/leading-bond.test.js +2 -2
- package/todo +2 -3
- /package/docs/readable-generated-code/{REFACTOR_PLAN.md → 20_REFACTOR_PLAN.md} +0 -0
- /package/docs/readable-generated-code/{EXECUTION_PLAN.md → 30_EXECUTION_PLAN.md} +0 -0
package/API.md
CHANGED
|
@@ -21,6 +21,7 @@ Create ring structures with substitutions and attachments.
|
|
|
21
21
|
| `substitutions` | `object` | `{}` | Position -> atom substitutions |
|
|
22
22
|
| `attachments` | `object` | `{}` | Position -> attachment list |
|
|
23
23
|
| `bonds` | `array` | `[]` | Bond types between atoms |
|
|
24
|
+
| `branchDepths` | `number[]` | `null` | Per-atom branch depth (for rings that cross branch boundaries) |
|
|
24
25
|
|
|
25
26
|
```javascript
|
|
26
27
|
// Simple benzene
|
|
@@ -61,7 +62,7 @@ const propene = Linear(['C', 'C', 'C'], [null, '=']);
|
|
|
61
62
|
// Ethanol with hydroxyl
|
|
62
63
|
const ethyl = Linear(['C', 'C']);
|
|
63
64
|
const hydroxyl = Linear(['O']);
|
|
64
|
-
const ethanol = ethyl.attach(
|
|
65
|
+
const ethanol = ethyl.attach(2, hydroxyl);
|
|
65
66
|
```
|
|
66
67
|
|
|
67
68
|
### `FusedRing(rings)`
|
|
@@ -107,7 +108,7 @@ All manipulation methods are **immutable** -- they return new nodes and never mo
|
|
|
107
108
|
const benzene = Ring({ atoms: 'c', size: 6 });
|
|
108
109
|
|
|
109
110
|
// Attach substituent at position
|
|
110
|
-
const toluene = benzene.attach(Linear(['C'])
|
|
111
|
+
const toluene = benzene.attach(1, Linear(['C']));
|
|
111
112
|
|
|
112
113
|
// Substitute atom at position
|
|
113
114
|
const pyridine = benzene.substitute(5, 'n');
|
|
@@ -117,20 +118,20 @@ const triazine = benzene.substituteMultiple({ 1: 'n', 3: 'n', 5: 'n' });
|
|
|
117
118
|
|
|
118
119
|
// Fuse with another ring (offset = shared atom count)
|
|
119
120
|
const ring2 = Ring({ atoms: 'C', size: 6 });
|
|
120
|
-
const naphthalene = benzene.fuse(
|
|
121
|
+
const naphthalene = benzene.fuse(2, ring2);
|
|
121
122
|
|
|
122
123
|
// Clone
|
|
123
124
|
const benzeneClone = benzene.clone();
|
|
124
125
|
```
|
|
125
126
|
|
|
126
|
-
#### `ring.attach(
|
|
127
|
+
#### `ring.attach(position, attachment, options?)`
|
|
127
128
|
|
|
128
129
|
Attach a node to the ring at a 1-indexed position.
|
|
129
130
|
|
|
130
131
|
| Parameter | Type | Description |
|
|
131
132
|
|-----------|------|-------------|
|
|
132
|
-
| `attachment` | `object` | Node to attach |
|
|
133
133
|
| `position` | `number` | 1-indexed ring position |
|
|
134
|
+
| `attachment` | `object` | Node to attach |
|
|
134
135
|
| `options.sibling` | `boolean` | If set, marks the attachment as sibling (true) or inline (false) |
|
|
135
136
|
|
|
136
137
|
#### `ring.substitute(position, newAtom)`
|
|
@@ -141,7 +142,7 @@ Replace the atom at position with a different atom symbol.
|
|
|
141
142
|
|
|
142
143
|
Replace multiple atoms. `substitutionMap` is `{ position: atomSymbol }`.
|
|
143
144
|
|
|
144
|
-
#### `ring.fuse(
|
|
145
|
+
#### `ring.fuse(offset, otherRing, options?)`
|
|
145
146
|
|
|
146
147
|
Fuse this ring with another ring. `offset` is how many positions into this ring the other ring starts.
|
|
147
148
|
|
|
@@ -156,7 +157,7 @@ const butane = Linear(['C', 'C', 'C', 'C']);
|
|
|
156
157
|
|
|
157
158
|
// Attach branch at position
|
|
158
159
|
const methyl = Linear(['C']);
|
|
159
|
-
const branched = butane.attach(
|
|
160
|
+
const branched = butane.attach(2, methyl);
|
|
160
161
|
|
|
161
162
|
// Concatenate chains
|
|
162
163
|
const hexane = butane.concat(Linear(['C', 'C']));
|
|
@@ -168,7 +169,7 @@ const isobutane = butane.branchAt({ 2: methyl });
|
|
|
168
169
|
const decorated = butane.branch(2, methyl, Linear(['O']));
|
|
169
170
|
```
|
|
170
171
|
|
|
171
|
-
#### `linear.attach(
|
|
172
|
+
#### `linear.attach(position, attachment)`
|
|
172
173
|
|
|
173
174
|
Attach a node at a 1-indexed position.
|
|
174
175
|
|
|
@@ -208,10 +209,10 @@ const combined = mol.concat(Molecule([Ring({ atoms: 'c', size: 6 })]));
|
|
|
208
209
|
### FusedRing Methods
|
|
209
210
|
|
|
210
211
|
```javascript
|
|
211
|
-
const fused = ring1.fuse(
|
|
212
|
+
const fused = ring1.fuse(4, ring2);
|
|
212
213
|
|
|
213
214
|
// Add another ring to the fused system
|
|
214
|
-
const triple = fused.addRing(
|
|
215
|
+
const triple = fused.addRing(8, ring3);
|
|
215
216
|
|
|
216
217
|
// Get a specific ring by number
|
|
217
218
|
const r = fused.getRing(1);
|
|
@@ -220,20 +221,45 @@ const r = fused.getRing(1);
|
|
|
220
221
|
const modified = fused.substituteInRing(1, 3, 'N');
|
|
221
222
|
|
|
222
223
|
// Attach to a specific ring
|
|
223
|
-
const decorated = fused.attachToRing(1, Linear(['O'])
|
|
224
|
+
const decorated = fused.attachToRing(1, 4, Linear(['O']));
|
|
224
225
|
|
|
225
226
|
// Renumber rings
|
|
226
227
|
const renumbered = fused.renumber(10);
|
|
227
228
|
|
|
228
|
-
// Add sequential continuation rings
|
|
229
|
+
// Add sequential continuation rings with depths and chain atoms
|
|
229
230
|
const withSeq = fused.addSequentialRings([ring3, ring4], {
|
|
230
|
-
|
|
231
|
+
depths: [1, 2],
|
|
232
|
+
chainAtoms: [
|
|
233
|
+
{ atom: 'C', depth: 2, position: 'before' },
|
|
234
|
+
{ atom: 'C', depth: 2, position: 'after', attachments: [Linear(['O'], ['='])] },
|
|
235
|
+
]
|
|
231
236
|
});
|
|
232
237
|
|
|
233
238
|
// Add attachment to a sequential atom position
|
|
234
239
|
const withAtt = fused.addSequentialAtomAttachment(25, Linear(['O']));
|
|
235
240
|
```
|
|
236
241
|
|
|
242
|
+
#### `fusedRing.addSequentialRings(rings, options?)`
|
|
243
|
+
|
|
244
|
+
Add continuation rings to a fused ring system. Computes all position metadata internally.
|
|
245
|
+
|
|
246
|
+
| Parameter | Type | Description |
|
|
247
|
+
|-----------|------|-------------|
|
|
248
|
+
| `rings` | `Ring[]` | Array of sequential Ring nodes to add |
|
|
249
|
+
| `options.depths` | `number[]` | Per-ring branch depth (e.g., `[1, 2, 1, 2]`) |
|
|
250
|
+
| `options.chainAtoms` | `object[]` | Standalone atoms between rings (see below) |
|
|
251
|
+
| `options.atomAttachments` | `object` | Legacy: position → attachment list map |
|
|
252
|
+
|
|
253
|
+
**chainAtoms entries:**
|
|
254
|
+
|
|
255
|
+
| Property | Type | Description |
|
|
256
|
+
|----------|------|-------------|
|
|
257
|
+
| `atom` | `string` | Atom symbol (e.g., `'C'`, `'N'`, `'O'`) |
|
|
258
|
+
| `depth` | `number` | Branch depth for this atom |
|
|
259
|
+
| `position` | `'before'` \| `'after'` | Whether the atom appears before or after rings at its depth |
|
|
260
|
+
| `bond` | `string` | Optional bond type (e.g., `'='`, `'#'`) |
|
|
261
|
+
| `attachments` | `object[]` | Optional array of nodes attached to this atom |
|
|
262
|
+
|
|
237
263
|
---
|
|
238
264
|
|
|
239
265
|
## Parsing & Serialization
|
|
@@ -363,7 +389,7 @@ import {
|
|
|
363
389
|
} from 'smiles-js/manipulation';
|
|
364
390
|
|
|
365
391
|
const benzene = Ring({ atoms: 'c', size: 6 });
|
|
366
|
-
const toluene = ringAttach(benzene, Linear(['C'])
|
|
392
|
+
const toluene = ringAttach(benzene, 1, Linear(['C']));
|
|
367
393
|
```
|
|
368
394
|
|
|
369
395
|
---
|
|
@@ -395,7 +421,7 @@ import RDKit from '@rdkit/rdkit';
|
|
|
395
421
|
// Build molecule programmatically
|
|
396
422
|
const benzene = Ring({ atoms: 'c', size: 6 });
|
|
397
423
|
const methyl = Linear(['C']);
|
|
398
|
-
const toluene = benzene.attach(
|
|
424
|
+
const toluene = benzene.attach(1, methyl);
|
|
399
425
|
|
|
400
426
|
// Use with RDKit
|
|
401
427
|
const rdkit = await RDKit.load();
|
|
@@ -435,9 +461,6 @@ Some complex molecules may have minor notation differences during round-trip:
|
|
|
435
461
|
|
|
436
462
|
**Impact**: Low - Structure is preserved, only notation differs.
|
|
437
463
|
|
|
438
|
-
### toCode()
|
|
464
|
+
### toCode() Generated Code
|
|
439
465
|
|
|
440
|
-
The `.toCode()` method
|
|
441
|
-
- Parsing SMILES -> AST
|
|
442
|
-
- Serializing AST -> SMILES
|
|
443
|
-
- Round-trip fidelity
|
|
466
|
+
The `.toCode()` method generates JavaScript constructor code that reconstructs the molecule. For molecules with sequential continuation rings (e.g., Telmisartan), the generated code uses `addSequentialRings()` with `depths` and `chainAtoms` options to produce clean structural API calls with no raw metadata assignments.
|
package/README.md
CHANGED
|
@@ -64,7 +64,7 @@ console.log(benzene.smiles); // c1ccccc1
|
|
|
64
64
|
|
|
65
65
|
// Add methyl group to make toluene
|
|
66
66
|
const methyl = Linear(['C']);
|
|
67
|
-
const toluene = benzene.attach(
|
|
67
|
+
const toluene = benzene.attach(1, methyl);
|
|
68
68
|
console.log(toluene.smiles); // c1(C)ccccc1
|
|
69
69
|
|
|
70
70
|
// Create pyridine via substitution
|
package/docs/EXAMPLES.md
CHANGED
|
@@ -108,7 +108,7 @@ node docs/atorvastatin-named.js
|
|
|
108
108
|
// Isopropyl group (substituent on pyrrole)
|
|
109
109
|
const methyl1 = Linear(['C']);
|
|
110
110
|
const ethyl = Linear(['C', 'C']);
|
|
111
|
-
const isopropyl = ethyl.attach(
|
|
111
|
+
const isopropyl = ethyl.attach(2, methyl1);
|
|
112
112
|
|
|
113
113
|
// Central pyrrole ring (5-membered nitrogen heterocycle)
|
|
114
114
|
const pyrroleRing = Ring({ atoms: 'c', size: 5 });
|
|
@@ -117,7 +117,7 @@ const pyrrole = pyrroleRing.substitute(5, 'n');
|
|
|
117
117
|
// Dihydroxyheptanoic acid side chain (the "statin" pharmacophore)
|
|
118
118
|
const heptanoicAcidChain = Linear(['C', 'C', 'C', 'C', 'C', 'C', 'C', 'O']);
|
|
119
119
|
const hydroxyl1 = Linear(['O']);
|
|
120
|
-
const statinSideChain = heptanoicAcidChain.attach(
|
|
120
|
+
const statinSideChain = heptanoicAcidChain.attach(3, hydroxyl1);
|
|
121
121
|
```
|
|
122
122
|
|
|
123
123
|
---
|
|
@@ -78,7 +78,7 @@ console.log(propane.smiles); // 'CCC'
|
|
|
78
78
|
```javascript
|
|
79
79
|
const benzene = Ring({ atoms: 'c', size: 6 });
|
|
80
80
|
const methyl = Linear(['C']);
|
|
81
|
-
const toluene = benzene.attach(
|
|
81
|
+
const toluene = benzene.attach(1, methyl);
|
|
82
82
|
console.log(toluene.smiles); // 'c1c(C)cccc1' or equivalent
|
|
83
83
|
```
|
|
84
84
|
|
|
@@ -197,7 +197,7 @@ const triazine = benzene.substituteMultiple({ 1: 'n', 3: 'n', 5: 'n' });
|
|
|
197
197
|
|
|
198
198
|
const ring1 = Ring({ atoms: 'C', size: 10 });
|
|
199
199
|
const ring2 = Ring({ atoms: 'C', size: 6 });
|
|
200
|
-
const naphthalene = ring1.fuse(
|
|
200
|
+
const naphthalene = ring1.fuse(2, ring2);
|
|
201
201
|
```
|
|
202
202
|
|
|
203
203
|
---
|
|
@@ -387,7 +387,7 @@ console.log(fragment.toCode());
|
|
|
387
387
|
// Output:
|
|
388
388
|
// const linear1 = Linear(['C'])
|
|
389
389
|
// const ring1 = Ring({ atoms: 'c', size: 6 })
|
|
390
|
-
// const ring2 = ring1.attach(
|
|
390
|
+
// const ring2 = ring1.attach(4, linear1)
|
|
391
391
|
```
|
|
392
392
|
|
|
393
393
|
---
|
|
@@ -780,10 +780,10 @@ FusedRing.prototype.substituteInRing = function(ringNumber, position, newAtom) {
|
|
|
780
780
|
return replaceRingInFusedSystem(this, ringNumber, updatedRing);
|
|
781
781
|
};
|
|
782
782
|
|
|
783
|
-
FusedRing.prototype.attachToRing = function(ringNumber,
|
|
783
|
+
FusedRing.prototype.attachToRing = function(ringNumber, position, attachment) {
|
|
784
784
|
// Attach to a specific ring in the fused system
|
|
785
785
|
const targetRing = this.getRing(ringNumber);
|
|
786
|
-
const updatedRing = targetRing.attach(
|
|
786
|
+
const updatedRing = targetRing.attach(position, attachment);
|
|
787
787
|
return replaceRingInFusedSystem(this, ringNumber, updatedRing);
|
|
788
788
|
};
|
|
789
789
|
|
|
@@ -832,7 +832,7 @@ Linear.prototype.branchAt = function(branchMap) {
|
|
|
832
832
|
// branchMap: { position: branch, ... }
|
|
833
833
|
let result = this;
|
|
834
834
|
for (const [position, branchStructure] of Object.entries(branchMap)) {
|
|
835
|
-
result = result.attach(
|
|
835
|
+
result = result.attach(position, branchStructure);
|
|
836
836
|
}
|
|
837
837
|
return result;
|
|
838
838
|
};
|
|
@@ -943,7 +943,7 @@ const methyl = Linear(['C']);
|
|
|
943
943
|
console.log(methyl.smiles); // 'C'
|
|
944
944
|
|
|
945
945
|
// Combined structures
|
|
946
|
-
const toluene = benzene.attach(
|
|
946
|
+
const toluene = benzene.attach(1, methyl);
|
|
947
947
|
console.log(toluene.smiles); // 'c1ccccc1C' or 'c1c(C)cccc1' (depending on serialization)
|
|
948
948
|
|
|
949
949
|
// Fused ring SMILES
|
|
@@ -964,7 +964,7 @@ console.log(ring1.smiles); // 'c1ccccc1'
|
|
|
964
964
|
const linear1 = Linear(['C']);
|
|
965
965
|
console.log(linear1.smiles); // 'C'
|
|
966
966
|
|
|
967
|
-
const molecule1 = ring1.attach(
|
|
967
|
+
const molecule1 = ring1.attach(1, linear1);
|
|
968
968
|
console.log(molecule1.smiles); // 'c1c(C)cccc1' (toluene)
|
|
969
969
|
|
|
970
970
|
// Chained operations with SMILES inspection at each step
|
|
@@ -978,7 +978,7 @@ const step2 = step1.substitute(3, 'n');
|
|
|
978
978
|
console.log('Step 2:', step2.smiles); // 'c1nccncc1'
|
|
979
979
|
|
|
980
980
|
const branch = Linear(['C', 'C']);
|
|
981
|
-
const final = step2.attach(
|
|
981
|
+
const final = step2.attach(5, branch);
|
|
982
982
|
console.log('Final:', final.smiles); // 'c1nccnc(CC)c1'
|
|
983
983
|
```
|
|
984
984
|
|
|
@@ -1005,7 +1005,7 @@ console.log('Final:', final.smiles); // 'c1nccnc(CC)c1'
|
|
|
1005
1005
|
// Create benzene and add methyl group
|
|
1006
1006
|
const benzene = Ring({ atoms: 'c', size: 6 });
|
|
1007
1007
|
const methyl = Linear(['C']);
|
|
1008
|
-
const toluene = benzene.attach(
|
|
1008
|
+
const toluene = benzene.attach(1, methyl);
|
|
1009
1009
|
|
|
1010
1010
|
// Create pyridine via substitution
|
|
1011
1011
|
const pyridine = benzene.substitute(1, 'n');
|
|
@@ -1016,7 +1016,7 @@ const triazine = benzene.substituteMultiple({ 1: 'n', 3: 'n', 5: 'n' });
|
|
|
1016
1016
|
// Fuse two rings
|
|
1017
1017
|
const ring1 = Ring({ atoms: 'C', size: 10 });
|
|
1018
1018
|
const ring2 = Ring({ atoms: 'C', size: 6 });
|
|
1019
|
-
const naphthalene = ring1.fuse(
|
|
1019
|
+
const naphthalene = ring1.fuse(2, ring2);
|
|
1020
1020
|
|
|
1021
1021
|
// ============================================
|
|
1022
1022
|
// FUSED RING MANIPULATION
|
|
@@ -1034,10 +1034,10 @@ const substituted = benzimidazole
|
|
|
1034
1034
|
.substituteInRing(1, 7, 'n');
|
|
1035
1035
|
|
|
1036
1036
|
// Attach to specific ring in fused system
|
|
1037
|
-
const withMethyl = benzimidazole.attachToRing(1, Linear(['C'])
|
|
1037
|
+
const withMethyl = benzimidazole.attachToRing(1, 1, Linear(['C']));
|
|
1038
1038
|
|
|
1039
1039
|
// Add another ring to the fused system
|
|
1040
|
-
const expandedSystem = naphthalene.addRing(Ring({ atoms: 'C', size: 5 })
|
|
1040
|
+
const expandedSystem = naphthalene.addRing(8, Ring({ atoms: 'C', size: 5 }));
|
|
1041
1041
|
|
|
1042
1042
|
// ============================================
|
|
1043
1043
|
// LINEAR CHAIN MANIPULATION
|
|
@@ -1082,12 +1082,12 @@ const phenyl2 = Ring({ atoms: 'C', size: 6 });
|
|
|
1082
1082
|
const carboxyl = Linear(['C'], ['=O', '-O']);
|
|
1083
1083
|
|
|
1084
1084
|
// Fluent chaining
|
|
1085
|
-
const phenylWithCarboxyl = phenyl2.attach(
|
|
1086
|
-
const biphenyl = phenyl1.attach(
|
|
1085
|
+
const phenylWithCarboxyl = phenyl2.attach(6, carboxyl);
|
|
1086
|
+
const biphenyl = phenyl1.attach(6, phenylWithCarboxyl);
|
|
1087
1087
|
const connector = Linear(['C', 'C']);
|
|
1088
1088
|
const sideChain = connector.concat(biphenyl);
|
|
1089
1089
|
|
|
1090
|
-
const coreWithSideChain = benzimidazoleCore.attachToRing(1,
|
|
1090
|
+
const coreWithSideChain = benzimidazoleCore.attachToRing(1, 9, sideChain);
|
|
1091
1091
|
|
|
1092
1092
|
// Final assembly using Molecule
|
|
1093
1093
|
const telmisartan = Molecule([
|
|
@@ -1476,7 +1476,7 @@ console.log(toluene.toCode());
|
|
|
1476
1476
|
// Output:
|
|
1477
1477
|
// const linear1 = Linear(['C'])
|
|
1478
1478
|
// const ring1 = Ring({ atoms: 'c', size: 6 })
|
|
1479
|
-
// const ring2 = ring1.attach(
|
|
1479
|
+
// const ring2 = ring1.attach(4, linear1)
|
|
1480
1480
|
```
|
|
1481
1481
|
|
|
1482
1482
|
**Decompiler Options:**
|
|
@@ -1493,7 +1493,7 @@ const options = {
|
|
|
1493
1493
|
Fragment('c1ccc(C)cc1').toCode(options);
|
|
1494
1494
|
// Output:
|
|
1495
1495
|
// const myLinear1 = Linear(['C'])
|
|
1496
|
-
// const myRing1 = Ring({ atoms: 'c', size: 6 }).attach(
|
|
1496
|
+
// const myRing1 = Ring({ atoms: 'c', size: 6 }).attach(4, myLinear1)
|
|
1497
1497
|
```
|
|
1498
1498
|
|
|
1499
1499
|
**Example Outputs:**
|
|
@@ -1507,7 +1507,7 @@ Fragment('c1ccccc1').toCode();
|
|
|
1507
1507
|
Fragment('c1ccc(C)cc1').toCode();
|
|
1508
1508
|
// const linear1 = Linear(['C'])
|
|
1509
1509
|
// const ring1 = Ring({ atoms: 'c', size: 6 })
|
|
1510
|
-
// const ring2 = ring1.attach(
|
|
1510
|
+
// const ring2 = ring1.attach(4, linear1)
|
|
1511
1511
|
|
|
1512
1512
|
// Pyridine: c1cccnc1
|
|
1513
1513
|
Fragment('c1cccnc1').toCode();
|
|
@@ -1530,9 +1530,9 @@ Fragment('CCCc1ccccc1').toCode();
|
|
|
1530
1530
|
Fragment('C(C(C))c1ccccc1').toCode();
|
|
1531
1531
|
// const linear1 = Linear(['C'])
|
|
1532
1532
|
// const linear2 = Linear(['C'])
|
|
1533
|
-
// const linear3 = linear1.attach(
|
|
1533
|
+
// const linear3 = linear1.attach(1, linear2)
|
|
1534
1534
|
// const linear4 = Linear(['C'])
|
|
1535
|
-
// const linear5 = linear3.attach(
|
|
1535
|
+
// const linear5 = linear3.attach(1, linear4)
|
|
1536
1536
|
// const ring1 = Ring({ atoms: 'c', size: 6 })
|
|
1537
1537
|
// const molecule1 = Molecule([linear5, ring1])
|
|
1538
1538
|
```
|
package/docs/PRODUCTION_AUDIT.md
CHANGED
|
@@ -53,7 +53,7 @@ CCC ✅
|
|
|
53
53
|
**Status**: COMPLETE
|
|
54
54
|
**Verification**:
|
|
55
55
|
```bash
|
|
56
|
-
$ node -e "import {Ring, Linear} from './src/index.js'; const benzene = Ring({ atoms: 'c', size: 6 }); const methyl = Linear(['C']); const toluene = benzene.attach(
|
|
56
|
+
$ node -e "import {Ring, Linear} from './src/index.js'; const benzene = Ring({ atoms: 'c', size: 6 }); const methyl = Linear(['C']); const toluene = benzene.attach(1, methyl); console.log(toluene.smiles);"
|
|
57
57
|
c1(C)ccccc1 ✅
|
|
58
58
|
```
|
|
59
59
|
- `Ring.prototype.attach()` works
|
|
@@ -228,7 +228,7 @@ const ring1 = Ring({ atoms: 'c', size: 6 }); ✅
|
|
|
228
228
|
$ node -e "import {parse} from './src/index.js'; const toluene = parse('c1ccc(C)cc1'); console.log(toluene.toCode());"
|
|
229
229
|
const ring1 = Ring({ atoms: 'c', size: 6 });
|
|
230
230
|
const ring2 = Linear(['C']);
|
|
231
|
-
const ring3 = ring1.attach(
|
|
231
|
+
const ring3 = ring1.attach(4, ring2); ✅
|
|
232
232
|
```
|
|
233
233
|
- All AST node types decompile correctly
|
|
234
234
|
- Substitutions generate method calls
|
|
@@ -296,25 +296,25 @@ Ran 457 tests across 25 files. [163.00ms]
|
|
|
296
296
|
- [x] Molecule(components)
|
|
297
297
|
|
|
298
298
|
### Ring Manipulation API ✅
|
|
299
|
-
- [x] ring.attach(
|
|
299
|
+
- [x] ring.attach(position, attachment)
|
|
300
300
|
- [x] ring.substitute(position, newAtom)
|
|
301
301
|
- [x] ring.substituteMultiple(substitutionMap)
|
|
302
|
-
- [x] ring.fuse(
|
|
302
|
+
- [x] ring.fuse(offset, otherRing)
|
|
303
303
|
- [x] ring.concat(other)
|
|
304
304
|
- [x] ring.clone()
|
|
305
305
|
|
|
306
306
|
### Linear Manipulation API ✅
|
|
307
|
-
- [x] linear.attach(
|
|
307
|
+
- [x] linear.attach(atomIndex, attachment)
|
|
308
308
|
- [x] linear.concat(other)
|
|
309
309
|
- [x] linear.branch(branchPoint, ...branches)
|
|
310
310
|
- [x] linear.branchAt(branchMap)
|
|
311
311
|
- [x] linear.clone()
|
|
312
312
|
|
|
313
313
|
### FusedRing Manipulation API ✅
|
|
314
|
-
- [x] fusedRing.addRing(
|
|
314
|
+
- [x] fusedRing.addRing(offset, ring)
|
|
315
315
|
- [x] fusedRing.getRing(ringNumber)
|
|
316
316
|
- [x] fusedRing.substituteInRing(ringNumber, position, newAtom)
|
|
317
|
-
- [x] fusedRing.attachToRing(ringNumber,
|
|
317
|
+
- [x] fusedRing.attachToRing(ringNumber, position, attachment)
|
|
318
318
|
- [x] fusedRing.renumber(startNumber)
|
|
319
319
|
- [x] fusedRing.concat(other)
|
|
320
320
|
- [x] fusedRing.clone()
|
|
@@ -215,8 +215,8 @@ const molecule2 = Ring({ atoms: 'c', size: 6 });
|
|
|
215
215
|
```javascript
|
|
216
216
|
const propylChain = Linear(['C', 'C', 'C']);
|
|
217
217
|
const benzeneRing = Ring({ atoms: 'c', size: 6 });
|
|
218
|
-
const isopropylGroup = propylChain.attach(
|
|
219
|
-
const phenylAmide = benzeneRing.attach(
|
|
218
|
+
const isopropylGroup = propylChain.attach(2, methylGroup);
|
|
219
|
+
const phenylAmide = benzeneRing.attach(1, amideLinker);
|
|
220
220
|
```
|
|
221
221
|
|
|
222
222
|
---
|
|
@@ -16,7 +16,7 @@ function log(...args) {
|
|
|
16
16
|
// Isopropyl group (substituent on pyrrole)
|
|
17
17
|
const methyl1 = Linear(['C']);
|
|
18
18
|
const ethyl = Linear(['C', 'C']);
|
|
19
|
-
const isopropyl = ethyl.attach(
|
|
19
|
+
const isopropyl = ethyl.attach(2, methyl1);
|
|
20
20
|
|
|
21
21
|
// Central pyrrole ring (5-membered nitrogen heterocycle)
|
|
22
22
|
const pyrroleRing = Ring({ atoms: 'c', size: 5 });
|
|
@@ -25,31 +25,31 @@ const pyrrole = pyrroleRing.substitute(5, 'n');
|
|
|
25
25
|
// Amide linker with phenyl group
|
|
26
26
|
const amideLinker = Linear(['C', 'N']);
|
|
27
27
|
const carbonyl = Linear(['O'], ['=']);
|
|
28
|
-
const amide = amideLinker.attach(
|
|
28
|
+
const amide = amideLinker.attach(1, carbonyl);
|
|
29
29
|
const phenylRing1 = Ring({ atoms: 'c', size: 6, ringNumber: 2 });
|
|
30
30
|
const phenylAmide = Molecule([amide, phenylRing1]);
|
|
31
31
|
|
|
32
32
|
// Attach amide-phenyl to pyrrole
|
|
33
|
-
const pyrroleWithAmide = pyrrole.attach(
|
|
33
|
+
const pyrroleWithAmide = pyrrole.attach(2, phenylAmide);
|
|
34
34
|
|
|
35
35
|
// Second phenyl ring (position 3)
|
|
36
36
|
const phenylRing2 = Ring({ atoms: 'c', size: 6, ringNumber: 3 });
|
|
37
|
-
const pyrroleWithTwoPhenyls = pyrroleWithAmide.attach(
|
|
37
|
+
const pyrroleWithTwoPhenyls = pyrroleWithAmide.attach(3, phenylRing2);
|
|
38
38
|
|
|
39
39
|
// Fluorophenyl ring (position 4)
|
|
40
40
|
const phenylRing3 = Ring({ atoms: 'c', size: 6, ringNumber: 4 });
|
|
41
41
|
const fluorine = Linear(['F']);
|
|
42
|
-
const fluorophenyl = phenylRing3.attach(
|
|
43
|
-
const pyrroleCore = pyrroleWithTwoPhenyls.attach(
|
|
42
|
+
const fluorophenyl = phenylRing3.attach(4, fluorine);
|
|
43
|
+
const pyrroleCore = pyrroleWithTwoPhenyls.attach(4, fluorophenyl);
|
|
44
44
|
|
|
45
45
|
// Dihydroxyheptanoic acid side chain (the "statin" pharmacophore)
|
|
46
46
|
const heptanoicAcidChain = Linear(['C', 'C', 'C', 'C', 'C', 'C', 'C', 'O']);
|
|
47
47
|
const hydroxyl1 = Linear(['O']);
|
|
48
|
-
const dihydroxyChain = heptanoicAcidChain.attach(
|
|
48
|
+
const dihydroxyChain = heptanoicAcidChain.attach(3, hydroxyl1);
|
|
49
49
|
const hydroxyl2 = Linear(['O']);
|
|
50
|
-
const trihydroxyChain = dihydroxyChain.attach(
|
|
50
|
+
const trihydroxyChain = dihydroxyChain.attach(5, hydroxyl2);
|
|
51
51
|
const carboxylate = Linear(['O'], ['=']);
|
|
52
|
-
const statinSideChain = trihydroxyChain.attach(
|
|
52
|
+
const statinSideChain = trihydroxyChain.attach(7, carboxylate);
|
|
53
53
|
|
|
54
54
|
// Assemble complete molecule
|
|
55
55
|
const atorvastatin = Molecule([isopropyl, pyrroleCore, statinSideChain]);
|
|
@@ -84,7 +84,7 @@ log('3️⃣ Methoxy groups: 2x', methoxyGroup1.smiles);
|
|
|
84
84
|
// Sulfoxide linker
|
|
85
85
|
const sulfoxideLinker = Linear(['S', 'C']);
|
|
86
86
|
const sulfinylOxygen = Linear(['O'], ['=']);
|
|
87
|
-
const sulfoxide = sulfoxideLinker.attach(
|
|
87
|
+
const sulfoxide = sulfoxideLinker.attach(1, sulfinylOxygen);
|
|
88
88
|
log('4️⃣ Sulfoxide linker: ', sulfoxide.smiles);
|
|
89
89
|
|
|
90
90
|
// Dimethoxymethylpyridine
|
package/docs/readable-generated-code/{READABLE_GENERATED_CODE.md → 10_READABLE_GENERATED_CODE.md}
RENAMED
|
@@ -11,7 +11,7 @@ export const v2 = Ring({ atoms: 'C', size: 5, bonds: ['=', null, '=', null, null
|
|
|
11
11
|
export const v3 = v2.substitute(2, 'N');
|
|
12
12
|
export const v4 = v3.substitute(5, 'N');
|
|
13
13
|
export const v5 = Ring({ atoms: 'C', size: 6, ringNumber: 2, offset: 2, bonds: ['=', null, '=', null, '=', null] });
|
|
14
|
-
export const v6 = v4.fuse(
|
|
14
|
+
export const v6 = v4.fuse(2, v5);
|
|
15
15
|
|
|
16
16
|
// Unreadable garbage - what is any of this?
|
|
17
17
|
v6.rings[0].metaPositions = [3, 4, 5, 10, 11];
|
|
@@ -102,7 +102,7 @@ export const v1 = Ring({ atoms: 'C', size: 5, bonds: ['=', null, '=', null, null
|
|
|
102
102
|
export const v2 = v1.substitute(2, 'N');
|
|
103
103
|
export const v3 = v2.substitute(5, 'N');
|
|
104
104
|
export const v4 = Ring({ atoms: 'C', size: 6, ringNumber: 2, offset: 2, bonds: ['=', null, '=', null, '=', null] });
|
|
105
|
-
export const v5 = v3.fuse(
|
|
105
|
+
export const v5 = v3.fuse(2, v4);
|
|
106
106
|
v5.rings[0].metaPositions = [3, 4, 5, 10, 11];
|
|
107
107
|
v5.rings[0].metaStart = 3;
|
|
108
108
|
v5.rings[0].metaEnd = 11;
|
|
@@ -119,7 +119,7 @@ export const v1 = Ring({ atoms: 'C', size: 5, bonds: ['=', null, '=', null, null
|
|
|
119
119
|
export const v2 = v1.substitute(2, 'N');
|
|
120
120
|
export const v3 = v2.substitute(5, 'N');
|
|
121
121
|
export const v4 = Ring({ atoms: 'C', size: 6, ringNumber: 2, offset: 2, bonds: ['=', null, '=', null, '=', null] });
|
|
122
|
-
export const v5 = v3.fuse(
|
|
122
|
+
export const v5 = v3.fuse(2, v4);
|
|
123
123
|
```
|
|
124
124
|
|
|
125
125
|
That's it. The `fuse()` method (and `FusedRing()` constructor) would call `layout()` internally, computing all the metadata automatically.
|
|
@@ -127,14 +127,14 @@ That's it. The `fuse()` method (and `FusedRing()` constructor) would call `layou
|
|
|
127
127
|
For cases with sequential rings, we'd need a new API method:
|
|
128
128
|
|
|
129
129
|
```javascript
|
|
130
|
-
export const v5 = v3.fuse(
|
|
130
|
+
export const v5 = v3.fuse(2, v4);
|
|
131
131
|
export const v6 = Ring({ atoms: 'C', size: 5, ringNumber: 5, bonds: ['=', null, '=', null, null] });
|
|
132
132
|
export const v7 = v6.substitute(2, 'N');
|
|
133
133
|
export const v8 = v7.substitute(5, 'N');
|
|
134
134
|
export const v9 = v5.addSequentialRing(v8);
|
|
135
135
|
|
|
136
136
|
// Or alternatively, pass sequential rings at construction time:
|
|
137
|
-
export const v5 = v3.fuse(
|
|
137
|
+
export const v5 = v3.fuse(2, v4, {
|
|
138
138
|
sequentialRings: [v8, v10, v11, v12],
|
|
139
139
|
sequentialAtomAttachments: { 25: [v13] }
|
|
140
140
|
});
|
|
@@ -212,7 +212,7 @@ export const v2 = Ring({ atoms: 'C', size: 5, bonds: ['=', null, '=', null, null
|
|
|
212
212
|
export const v3 = v2.substitute(2, 'N');
|
|
213
213
|
export const v4 = v3.substitute(5, 'N');
|
|
214
214
|
export const v5 = Ring({ atoms: 'C', size: 6, ringNumber: 2, offset: 2, bonds: ['=', null, '=', null, '=', null], branchDepths: [0, 0, 1, 1, 2, 2] });
|
|
215
|
-
export const v6 = v4.fuse(
|
|
215
|
+
export const v6 = v4.fuse(2, v5);
|
|
216
216
|
|
|
217
217
|
export const v7 = Ring({ atoms: 'C', size: 5, ringNumber: 5, bonds: ['=', null, '=', null, null] });
|
|
218
218
|
export const v8 = v7.substitute(2, 'N');
|
|
@@ -30,7 +30,7 @@ export const v1 = Ring({ atoms: 'C', size: 5, bonds: ['=', null, '=', null, null
|
|
|
30
30
|
export const v2 = v1.substitute(2, 'N');
|
|
31
31
|
export const v3 = v2.substitute(5, 'N');
|
|
32
32
|
export const v4 = Ring({ atoms: 'C', size: 6, ringNumber: 2, offset: 2 });
|
|
33
|
-
export const v5 = v3.fuse(
|
|
33
|
+
export const v5 = v3.fuse(2, v4);
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
Done. No meta. The `fuse()` call computes everything internally via `layout()`.
|
|
@@ -42,7 +42,7 @@ Done. No meta. The `fuse()` call computes everything internally via `layout()`.
|
|
|
42
42
|
export const v1 = Linear(['C', 'C', 'C']);
|
|
43
43
|
export const v2 = Ring({ atoms: 'C', size: 5, ... });
|
|
44
44
|
// ...
|
|
45
|
-
export let v6 = v4.fuse(
|
|
45
|
+
export let v6 = v4.fuse(2, v5);
|
|
46
46
|
// ...
|
|
47
47
|
v6 = v6.addSequentialRings([v9, v10, v11, v12], { atomAttachments: { 25: [v13] } });
|
|
48
48
|
|
|
@@ -135,9 +135,9 @@ Not everything is bad. Simple molecules produce perfectly readable code:
|
|
|
135
135
|
```javascript
|
|
136
136
|
export const v1 = Linear(['C', 'C', 'N']);
|
|
137
137
|
export const v2 = Ring({ atoms: 'c', size: 6 });
|
|
138
|
-
export const v3 = v2.attach(
|
|
138
|
+
export const v3 = v2.attach(3, v1);
|
|
139
139
|
export const v4 = Linear(['O']);
|
|
140
|
-
export const v5 = v3.attach(
|
|
140
|
+
export const v5 = v3.attach(1, v4);
|
|
141
141
|
export const v6 = Molecule([v5]);
|
|
142
142
|
```
|
|
143
143
|
|
|
@@ -165,7 +165,7 @@ For the simple path, the decompiler emits:
|
|
|
165
165
|
const v1 = Ring({ atoms: 'C', size: 5, bonds: [...], branchDepths: [...] });
|
|
166
166
|
const v2 = v1.substitute(2, 'N');
|
|
167
167
|
const v3 = Ring({ atoms: 'C', size: 6, ringNumber: 2, offset: 2, ... });
|
|
168
|
-
const v4 = v2.fuse(
|
|
168
|
+
const v4 = v2.fuse(2, v3);
|
|
169
169
|
```
|
|
170
170
|
|
|
171
171
|
No meta. The `branchDepths` option on Ring is structural (it's an input to the constructor, not computed metadata).
|
|
@@ -210,7 +210,7 @@ export const v2 = Ring({ atoms: 'C', size: 5, bonds: ['=', null, '=', null, null
|
|
|
210
210
|
export const v3 = v2.substitute(2, 'N');
|
|
211
211
|
export const v4 = v3.substitute(5, 'N');
|
|
212
212
|
export const v5 = Ring({ atoms: 'C', size: 6, ringNumber: 2, offset: 2, bonds: ['=', null, '=', null, '=', null], branchDepths: [0, 0, 1, 1, 2, 2] });
|
|
213
|
-
export const v6 = v4.fuse(
|
|
213
|
+
export const v6 = v4.fuse(2, v5);
|
|
214
214
|
// sequential rings become attachments or are added via addSequentialRings
|
|
215
215
|
// ...
|
|
216
216
|
// NO META LINES
|