diff --git a/package-lock.json b/package-lock.json index 0617ba1..e61fec1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,14 +28,14 @@ "fifo-logger": "^2.0.1", "globals": "^17.7.0", "mf-parser": "^3.9.1", - "openchemlib": "^9.23.0", + "openchemlib": "^9.24.0", "prettier": "^3.9.1", "rimraf": "^6.1.3", "typescript": "^6.0.3", "vitest": "^4.1.9" }, "peerDependencies": { - "openchemlib": ">=9.20.1" + "openchemlib": ">=9.24.0" } }, "node_modules/@babel/code-frame": { @@ -2054,9 +2054,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2074,9 +2071,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2094,9 +2088,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2114,9 +2105,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2134,9 +2122,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2154,9 +2139,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2517,9 +2499,6 @@ "arm" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2534,9 +2513,6 @@ "arm" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2551,9 +2527,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2568,9 +2541,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2585,9 +2555,6 @@ "loong64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2602,9 +2569,6 @@ "loong64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2619,9 +2583,6 @@ "ppc64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2636,9 +2597,6 @@ "ppc64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2653,9 +2611,6 @@ "riscv64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2670,9 +2625,6 @@ "riscv64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -2687,9 +2639,6 @@ "s390x" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2704,9 +2653,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MIT", "optional": true, "os": [ @@ -2721,9 +2667,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MIT", "optional": true, "os": [ @@ -6207,9 +6150,6 @@ "arm64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -6231,9 +6171,6 @@ "arm64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -6255,9 +6192,6 @@ "x64" ], "dev": true, - "libc": [ - "glibc" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -6279,9 +6213,6 @@ "x64" ], "dev": true, - "libc": [ - "musl" - ], "license": "MPL-2.0", "optional": true, "os": [ @@ -6728,9 +6659,9 @@ } }, "node_modules/openchemlib": { - "version": "9.23.0", - "resolved": "https://registry.npmjs.org/openchemlib/-/openchemlib-9.23.0.tgz", - "integrity": "sha512-kRFwKyXNl/ElbAoy41+dfbdZikJ7/0xcKm1bkciRD6dAeiq4Eb2TT04ePctCBBG26Iov+GgxeZqJHFLiG7Qp1Q==", + "version": "9.24.0", + "resolved": "https://registry.npmjs.org/openchemlib/-/openchemlib-9.24.0.tgz", + "integrity": "sha512-aIZc4wxcIq4d1RK32WAkGjjyFUKPcf2zdlklvzkmmo2HuHTojFUubeSxp0CQ6MZDGLiK18iQJwxA4/O9Db2bvA==", "dev": true, "license": "BSD-3-Clause" }, diff --git a/package.json b/package.json index 1e06e5e..8f34e9a 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "sdf-parser": "^9.0.0" }, "peerDependencies": { - "openchemlib": ">=9.20.1" + "openchemlib": ">=9.24.0" }, "devDependencies": { "@types/node": "^26.0.1", @@ -56,7 +56,7 @@ "fifo-logger": "^2.0.1", "globals": "^17.7.0", "mf-parser": "^3.9.1", - "openchemlib": "^9.23.0", + "openchemlib": "^9.24.0", "prettier": "^3.9.1", "rimraf": "^6.1.3", "typescript": "^6.0.3", diff --git a/src/topic/TopicMolecule.ts b/src/topic/TopicMolecule.ts index 4ae715b..1bf336f 100644 --- a/src/topic/TopicMolecule.ts +++ b/src/topic/TopicMolecule.ts @@ -100,6 +100,17 @@ interface GetHoseFragmentOptions { tagAtomFct?: (molecule: Molecule, iAtom: number) => undefined; } +export interface SetProchiralHydrogenLabelsOptions { + /** + * Also label enantiotopic CH2 hydrogens. They carry a genuine pro-R / pro-S + * descriptor but are equivalent in an achiral environment (e.g. the CH2 of + * ethanol), so they are skipped by default; diastereotopic (NMR-inequivalent) + * hydrogens are always labelled. + * @default false + */ + includeEnantiotopic?: boolean; +} + /** * This class deals with topicity information and hose codes * It is optimized to avoid recalculation of the same information @@ -518,27 +529,32 @@ export class TopicMolecule { } /** - * Set a pro-R / pro-S `customLabel` on each diastereotopic CH2 hydrogen, - * appended as lowercase `r` or `s`. Idempotent: calling it again replaces - * any trailing pro-R / pro-S letter rather than stacking. The label is - * appended to whatever `customLabel` the hydrogen already has, falling - * back to the parent carbon's `customLabel` when the hydrogen has none. - * Both `molecule` and `moleculeWithH` are labelled in place, sharing the - * same cached `prochiralityByEnantioID` hash; on `molecule` only hydrogens - * that are explicit (i.e. exist as atoms there) receive a label. - * Repeated calls do no extra CIP work. + * Set a pro-R / pro-S `customLabel` (lowercase `r` / `s`) on each prochiral + * CH2 hydrogen, in both `molecule` and `moleculeWithH`. The label is prefixed + * with `']'` so OCL renders it as a superscript, and appended to the + * hydrogen's existing `customLabel` (or the parent carbon's). By default only + * diastereotopic hydrogens are labelled; pass `includeEnantiotopic` to also + * label enantiotopic ones. Idempotent and does no extra CIP work on repeat. + * @param options - controls which prochiral hydrogens are labelled * @returns The number of hydrogens labelled in `moleculeWithH` */ - setProchiralHydrogenLabels(): number { + setProchiralHydrogenLabels( + options: SetProchiralHydrogenLabelsOptions = {}, + ): number { + const { includeEnantiotopic = false } = options; const moleculeWithH = this.moleculeWithH; const molecule = this.molecule; const moleculeAtoms = molecule.getAllAtoms(); const map = this.prochiralityByEnantioID; const enantioIDs = this.enantioIDs; + const diaIDs = includeEnantiotopic ? null : this.diaIDs; let count = 0; for (let atom = 0; atom < enantioIDs.length; atom++) { const letter = map[enantioIDs[atom]]; if (!letter) continue; + if (diaIDs && !isDiastereotopicHydrogen(moleculeWithH, diaIDs, atom)) { + continue; + } appendProchiralLabel( moleculeWithH, atom, @@ -561,7 +577,8 @@ export class TopicMolecule { /** * Remove a trailing pro-R / pro-S character (`r` or `s`) from the * `customLabel` of every hydrogen that carries one, in both `molecule` - * and `moleculeWithH`. Idempotent. Hydrogens whose `customLabel` does + * and `moleculeWithH`. A label left as just the superscript marker `']'` + * is removed entirely. Idempotent. Hydrogens whose `customLabel` does * not end with `r` or `s` are left untouched. * @returns The number of labels that were stripped from `moleculeWithH` */ @@ -676,6 +693,24 @@ function computeProchiralityByEnantioID( return result; } +// Two geminal CH2 hydrogens are diastereotopic when their (racemic) diaIDs +// differ; an equal diaID means they are enantiotopic (equivalent in an achiral +// environment) and should be skipped unless explicitly requested. +function isDiastereotopicHydrogen( + molecule: Molecule, + diaIDs: string[], + hydrogen: number, +): boolean { + const carbon = molecule.getConnAtom(hydrogen, 0); + for (let j = 0; j < molecule.getAllConnAtoms(carbon); j++) { + const connected = molecule.getConnAtom(carbon, j); + if (connected !== hydrogen && molecule.getAtomicNo(connected) === 1) { + return diaIDs[hydrogen] !== diaIDs[connected]; + } + } + return false; +} + function appendProchiralLabel( molecule: Molecule, hydrogen: number, @@ -686,16 +721,23 @@ function appendProchiralLabel( if (existing) { molecule.setAtomCustomLabel( hydrogen, - existing.replace(/[rs]$/, '') + letter, + toSuperscript(existing.replace(/[rs]$/, '')) + letter, ); return; } const parentLabel = molecule.getAtomCustomLabel(parent); if (parentLabel) { - molecule.setAtomCustomLabel(hydrogen, parentLabel + letter); + molecule.setAtomCustomLabel(hydrogen, toSuperscript(parentLabel) + letter); return; } - molecule.setAtomCustomLabel(hydrogen, letter); + molecule.setAtomCustomLabel(hydrogen, `]${letter}`); +} + +// OCL renders a custom label as a superscript at the top-left of the atom when +// it starts with ']'. Keep the prochiral descriptor (and any inherited prefix) +// superscript, matching the skeleton numbering produced by `applyFragmentLabels`. +function toSuperscript(label: string): string { + return label.startsWith(']') ? label : `]${label}`; } function stripTrailingProchiralLetter(molecule: Molecule): number { @@ -706,7 +748,10 @@ function stripTrailingProchiralLetter(molecule: Molecule): number { if (!customLabel) continue; const stripped = customLabel.replace(/[rs]$/, ''); if (stripped === customLabel) continue; - molecule.setAtomCustomLabel(atom, stripped || null); + molecule.setAtomCustomLabel( + atom, + stripped && stripped !== ']' ? stripped : null, + ); count++; } return count; diff --git a/src/topic/__tests__/TopicMolecule.setProchiralHydrogenLabels.test.js b/src/topic/__tests__/TopicMolecule.setProchiralHydrogenLabels.test.js index 5591b14..236aec0 100644 --- a/src/topic/__tests__/TopicMolecule.setProchiralHydrogenLabels.test.js +++ b/src/topic/__tests__/TopicMolecule.setProchiralHydrogenLabels.test.js @@ -92,7 +92,13 @@ test('CC[C@H](C)Cl: diaIDs and pro-R/pro-S assignment of the CH2 hydrogens', () const label0 = moleculeWithH.getAtomCustomLabel(ch2Hydrogens[0]); const label1 = moleculeWithH.getAtomCustomLabel(ch2Hydrogens[1]); - expect([label0, label1].toSorted()).toStrictEqual(['r', 's']); + expect([label0, label1].toSorted()).toStrictEqual([']r', ']s']); + + expect( + moleculeWithH.getCanonizedIDCode( + Molecule.CANONIZER_ENCODE_ATOM_CUSTOM_LABELS, + ), + ).toBe(String.raw`gNpLADV@\@dsUURbRC}De[n]muN`); // Snapshot captures the diaID → r/s mapping for verification in external software expect({ [diaID0]: label0, [diaID1]: label1 }).toMatchSnapshot(); @@ -138,7 +144,13 @@ test('CC(Cl)CC: the CH2 hydrogens get distinct pro-R / pro-S labels', () => { .map((l) => l.label) .toSorted(); - expect(labels).toStrictEqual(['r', 's']); + expect(labels).toStrictEqual([']r', ']s']); + + expect( + topicMolecule.moleculeWithH.getCanonizedIDCode( + Molecule.CANONIZER_ENCODE_ATOM_CUSTOM_LABELS, + ), + ).toBe(String.raw`gNpLADV@\@dsUUReBP_hdk]smnip`); }); test('CC1CCC1 (methylcyclobutane): the four CH2 hydrogens at C2/C4 are labelled, the apex CH2 is not', () => { @@ -152,7 +164,13 @@ test('CC1CCC1 (methylcyclobutane): the four CH2 hydrogens at C2/C4 are labelled, .map((l) => l.label) .toSorted(); - expect(labels).toStrictEqual(['r', 'r', 's', 's']); + expect(labels).toStrictEqual([']r', ']r', ']s', ']s']); + + expect( + topicMolecule.moleculeWithH.getCanonizedIDCode( + Molecule.CANONIZER_ENCODE_ATOM_CUSTOM_LABELS, + ), + ).toBe('did@HL`B`N`A`LddTlRjjjdAT`_hRXw\\yMuOs]sbwTx'); }); test('CC(Cl)CC: heavy-atom customLabel is inherited by the labelled hydrogens', () => { @@ -178,10 +196,52 @@ test('CC(Cl)CC: heavy-atom customLabel is inherited by the labelled hydrogens', topicMolecule.setProchiralHydrogenLabels(); const labels = getHydrogenLabels(topicMolecule) .map((l) => l.label) - .filter((l) => l.startsWith('3')) + .filter((l) => l.startsWith(']3')) .toSorted(); - expect(labels).toStrictEqual(['3r', '3s']); + expect(labels).toStrictEqual([']3r', ']3s']); + + expect( + moleculeWithH.getCanonizedIDCode( + Molecule.CANONIZER_ENCODE_ATOM_CUSTOM_LABELS, + ), + ).toBe('gNpLADV@\\@dsUUReBP_idUfo]sYww\\r\\'); +}); + +test('CCO (ethanol): enantiotopic CH2 hydrogens are not labelled by default', () => { + const molecule = Molecule.fromSmiles('CCO'); + const topicMolecule = new TopicMolecule(molecule); + + expect(topicMolecule.setProchiralHydrogenLabels()).toBe(0); + + const labels = getHydrogenLabels(topicMolecule).map((l) => l.label); + + expect(labels).toStrictEqual([]); +}); + +test('CCO (ethanol): enantiotopic CH2 hydrogens are labelled with includeEnantiotopic', () => { + const molecule = Molecule.fromSmiles('CCO'); + const topicMolecule = new TopicMolecule(molecule); + + expect( + topicMolecule.setProchiralHydrogenLabels({ includeEnantiotopic: true }), + ).toBe(2); + + const labels = getHydrogenLabels(topicMolecule) + .map((l) => l.label) + .toSorted(); + + expect(labels).toStrictEqual([']r', ']s']); +}); + +test('OCc1ccccc1 (benzyl alcohol): enantiotopic CH2 is skipped by default, kept when requested', () => { + const def = new TopicMolecule(Molecule.fromSmiles('OCc1ccccc1')); + + expect(def.setProchiralHydrogenLabels()).toBe(0); + + const all = new TopicMolecule(Molecule.fromSmiles('OCc1ccccc1')); + + expect(all.setProchiralHydrogenLabels({ includeEnantiotopic: true })).toBe(2); }); test('prochiralities is an atom-indexed cached array of r/s/undefined', () => { @@ -254,7 +314,7 @@ test('CC(Cl)CC: implicit-H molecule gets no labels, only moleculeWithH does', () .map((l) => l.label) .toSorted(); - expect(inMoleculeWithH).toStrictEqual(['r', 's']); + expect(inMoleculeWithH).toStrictEqual([']r', ']s']); }); test('explicit-H molecule: both molecule and moleculeWithH get labels', () => { @@ -271,13 +331,13 @@ test('explicit-H molecule: both molecule and moleculeWithH get labels', () => { if (label) inMolecule.push(label); } - expect(inMolecule.toSorted()).toStrictEqual(['r', 's']); + expect(inMolecule.toSorted()).toStrictEqual([']r', ']s']); const inMoleculeWithH = getHydrogenLabels(topicMolecule) .map((l) => l.label) .toSorted(); - expect(inMoleculeWithH).toStrictEqual(['r', 's']); + expect(inMoleculeWithH).toStrictEqual([']r', ']s']); // removeProchiralHydrogenLabels strips both targets. topicMolecule.removeProchiralHydrogenLabels(); @@ -303,7 +363,7 @@ test('setProchiralHydrogenLabels replaces an existing trailing r/s rather than s .map((l) => l.label) .toSorted(); - expect(labels).toStrictEqual(['r', 's']); + expect(labels).toStrictEqual([']r', ']s']); }); test('setProchiralHydrogenLabels preserves a heavy-atom prefix when retagging', () => { @@ -331,10 +391,10 @@ test('setProchiralHydrogenLabels preserves a heavy-atom prefix when retagging', const labels = getHydrogenLabels(topicMolecule) .map((l) => l.label) - .filter((l) => l.startsWith('3')) + .filter((l) => l.startsWith(']3')) .toSorted(); - expect(labels).toStrictEqual(['3r', '3s']); + expect(labels).toStrictEqual([']3r', ']3s']); }); test('removeProchiralHydrogenLabels strips the trailing pro-R / pro-S letter', () => { @@ -375,5 +435,5 @@ test('removeProchiralHydrogenLabels is idempotent and preserves the heavy-atom p .map((l) => l.label) .toSorted(); - expect(labels).toStrictEqual(['3', '3']); + expect(labels).toStrictEqual([']3', ']3']); }); diff --git a/src/topic/__tests__/__snapshots__/TopicMolecule.setProchiralHydrogenLabels.test.js.snap b/src/topic/__tests__/__snapshots__/TopicMolecule.setProchiralHydrogenLabels.test.js.snap index 80c7fc8..8ef7b8e 100644 --- a/src/topic/__tests__/__snapshots__/TopicMolecule.setProchiralHydrogenLabels.test.js.snap +++ b/src/topic/__tests__/__snapshots__/TopicMolecule.setProchiralHydrogenLabels.test.js.snap @@ -2,7 +2,7 @@ exports[`CC[C@H](C)Cl: diaIDs and pro-R/pro-S assignment of the CH2 hydrogens 1`] = ` { - "gGPDALfHRYjjThQ@_iDBIU@": "s", - "gGPDALfHRYjjThU@_iDBIU@": "r", + "gGPDALfHRYjjThQ@_iDBIU@": "]s", + "gGPDALfHRYjjThU@_iDBIU@": "]r", } `; diff --git a/src/topic/__tests__/__snapshots__/getEnantioIDs.test.js.snap b/src/topic/__tests__/__snapshots__/getEnantioIDs.test.js.snap index 36f94f1..3813112 100644 --- a/src/topic/__tests__/__snapshots__/getEnantioIDs.test.js.snap +++ b/src/topic/__tests__/__snapshots__/getEnantioIDs.test.js.snap @@ -3,27 +3,27 @@ exports[`butan-1-ol (CCCCO): all three CH2 groups are enantiotopic and prochiral 1`] = ` { "gGQHBIeIejjQA~dPHeT": { - "proChirality": "s", + "proChirality": "]s", "smiles": "[H]C([H])(C)[C@]([H])([2H])C([H])([H])O", }, "gGQHBIeIejjRA~dPHeT": { - "proChirality": "r", + "proChirality": "]r", "smiles": "[H]C([H])(C)[C@@]([H])([2H])C([H])([H])O", }, "gGQHDIeImjjQA~dPHeT": { - "proChirality": "s", + "proChirality": "]s", "smiles": "[H]C([H])(C)C([H])([H])[C@]([H])([2H])O", }, "gGQHDIeImjjRA~dPHeT": { - "proChirality": "r", + "proChirality": "]r", "smiles": "[H]C([H])(C)C([H])([H])[C@@]([H])([2H])O", }, "gGQHJIeIgjjQA~dPHeT": { - "proChirality": "s", + "proChirality": "]s", "smiles": "[H]C([H])(C([H])([H])O)[C@@]([H])([2H])C", }, "gGQHJIeIgjjRA~dPHeT": { - "proChirality": "r", + "proChirality": "]r", "smiles": "[H]C([H])(C([H])([H])O)[C@]([H])([2H])C", }, } @@ -32,11 +32,11 @@ exports[`butan-1-ol (CCCCO): all three CH2 groups are enantiotopic and prochiral exports[`ethanol (CCO): the CH2 hydrogens are enantiotopic — same diaID, different enantioID 1`] = ` { "gCaHDIeIjiDGzQ@bUP": { - "proChirality": "s", + "proChirality": "]s", "smiles": "[H][C@]([2H])(C)O", }, "gCaHDIeIjiHGzQ@bUP": { - "proChirality": "r", + "proChirality": "]r", "smiles": "[H][C@@]([2H])(C)O", }, } @@ -45,19 +45,19 @@ exports[`ethanol (CCO): the CH2 hydrogens are enantiotopic — same diaID, diffe exports[`propan-1-ol (CCCO): both CH2 groups are enantiotopic and prochiral 1`] = ` { "gJQHBIeIfjdP_iDBIU@": { - "proChirality": "s", + "proChirality": "]s", "smiles": "[H]C([H])([C@@]([H])([2H])C)O", }, "gJQHBIeIfjd\`_iDBIU@": { - "proChirality": "r", + "proChirality": "]r", "smiles": "[H]C([H])([C@]([H])([2H])C)O", }, "gJQHDIeInjdP_iDBIU@": { - "proChirality": "s", + "proChirality": "]s", "smiles": "[H]C([H])(C)[C@]([H])([2H])O", }, "gJQHDIeInjd\`_iDBIU@": { - "proChirality": "r", + "proChirality": "]r", "smiles": "[H]C([H])(C)[C@@]([H])([2H])O", }, } diff --git a/src/topic/__tests__/getEnantioIDs.test.js b/src/topic/__tests__/getEnantioIDs.test.js index b7cca06..ef1c210 100644 --- a/src/topic/__tests__/getEnantioIDs.test.js +++ b/src/topic/__tests__/getEnantioIDs.test.js @@ -99,7 +99,9 @@ test('ethanol (CCO): the CH2 hydrogens are enantiotopic — same diaID, differen expect(new Set(topicMolecule.diaIDs).size).toBe(6); expect(new Set(enantioIDs).size).toBe(7); - const count = topicMolecule.setProchiralHydrogenLabels(); + const count = topicMolecule.setProchiralHydrogenLabels({ + includeEnantiotopic: true, + }); expect(count).toBe(2); @@ -110,7 +112,7 @@ test('ethanol (CCO): the CH2 hydrogens are enantiotopic — same diaID, differen ch2.hydrogens[1], ); - expect([label0, label1].toSorted()).toStrictEqual(['r', 's']); + expect([label0, label1].toSorted()).toStrictEqual([']r', ']s']); // The exact enantioID → pro-R / pro-S mapping, for verification in external // software. Snapshotted because idCodes can contain non-printable bytes. The @@ -132,6 +134,15 @@ test('ethanol (CCO): the CH2 hydrogens are enantiotopic — same diaID, differen ), }, }).toMatchSnapshot(); + + // getCanonizedIDCode reorders the atoms in place (labelled hydrogens leave + // the trailing-hydrogen block), so assert it after everything that reads + // moleculeWithH atom indices. + expect( + topicMolecule.moleculeWithH.getCanonizedIDCode( + Molecule.CANONIZER_ENCODE_ATOM_CUSTOM_LABELS, + ), + ).toBe('gJQDAh@pBSUUH`\u007FQIf{gK]S`'); }); test('propan-1-ol (CCCO): both CH2 groups are enantiotopic and prochiral', () => { @@ -156,14 +167,16 @@ test('propan-1-ol (CCCO): both CH2 groups are enantiotopic and prochiral', () => expect(new Set(topicMolecule.diaIDs).size).toBe(8); expect(new Set(topicMolecule.enantioIDs).size).toBe(10); - const count = topicMolecule.setProchiralHydrogenLabels(); + const count = topicMolecule.setProchiralHydrogenLabels({ + includeEnantiotopic: true, + }); expect(count).toBe(4); expect(getSortedHydrogenLabels(topicMolecule)).toStrictEqual([ - 'r', - 'r', - 's', - 's', + ']r', + ']r', + ']s', + ']s', ]); const mapping = {}; @@ -182,6 +195,15 @@ test('propan-1-ol (CCCO): both CH2 groups are enantiotopic and prochiral', () => } expect(mapping).toMatchSnapshot(); + + // getCanonizedIDCode reorders the atoms in place (labelled hydrogens leave + // the trailing-hydrogen block), so assert it after everything that reads + // moleculeWithH atom indices. + expect( + topicMolecule.moleculeWithH.getCanonizedIDCode( + Molecule.CANONIZER_ENCODE_ATOM_CUSTOM_LABELS, + ), + ).toBe('daxHH@r@J@Z@z@RZZjjdaH`_hRXw\\yMuNs]s|wTx'); }); test('butan-1-ol (CCCCO): all three CH2 groups are enantiotopic and prochiral', () => { @@ -206,16 +228,18 @@ test('butan-1-ol (CCCCO): all three CH2 groups are enantiotopic and prochiral', expect(new Set(topicMolecule.diaIDs).size).toBe(10); expect(new Set(topicMolecule.enantioIDs).size).toBe(13); - const count = topicMolecule.setProchiralHydrogenLabels(); + const count = topicMolecule.setProchiralHydrogenLabels({ + includeEnantiotopic: true, + }); expect(count).toBe(6); expect(getSortedHydrogenLabels(topicMolecule)).toStrictEqual([ - 'r', - 'r', - 'r', - 's', - 's', - 's', + ']r', + ']r', + ']r', + ']s', + ']s', + ']s', ]); const mapping = {}; @@ -234,6 +258,15 @@ test('butan-1-ol (CCCCO): all three CH2 groups are enantiotopic and prochiral', } expect(mapping).toMatchSnapshot(); + + // getCanonizedIDCode reorders the atoms in place (labelled hydrogens leave + // the trailing-hydrogen block), so assert it after everything that reads + // moleculeWithH atom indices. + expect( + topicMolecule.moleculeWithH.getCanonizedIDCode( + Molecule.CANONIZER_ENCODE_ATOM_CUSTOM_LABELS, + ), + ).toBe('dmTHX@r@J@Z@z@f@V@rJQRFRjjjrDbi@\u007FQdqnyr[j]f{gyniy[n]Vzg@'); }); test('getEnantioIDs racemic option collapses the enantiotopic CH2 hydrogens', () => {