From 9dfe3e8ef463db446ff5f4b61a3ceb7219eb7648 Mon Sep 17 00:00:00 2001 From: Luc Patiny Date: Mon, 13 May 2024 08:46:26 +0200 Subject: [PATCH 1/8] chore: add @types/jest --- package-lock.json | 11 +++++++++++ package.json | 1 + 2 files changed, 12 insertions(+) diff --git a/package-lock.json b/package-lock.json index d3b98c2b..e3f7009c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "8.9.0", "license": "BSD-3-Clause", "devDependencies": { + "@types/jest": "^29.5.12", "benchmark": "^2.1.4", "eslint": "^8.56.0", "eslint-config-cheminfo": "^9.1.1", @@ -1355,6 +1356,16 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/jest": { + "version": "29.5.12", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz", + "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==", + "dev": true, + "dependencies": { + "expect": "^29.0.0", + "pretty-format": "^29.0.0" + } + }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", diff --git a/package.json b/package.json index 6460f3bf..7b3ba271 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,7 @@ }, "homepage": "https://github.com/cheminfo/openchemlib-js", "devDependencies": { + "@types/jest": "^29.5.12", "benchmark": "^2.1.4", "eslint": "^8.56.0", "eslint-config-cheminfo": "^9.1.1", From d472f03c9addb7af5070194e9e30bd0a230b9c55 Mon Sep 17 00:00:00 2001 From: Luc Patiny Date: Mon, 13 May 2024 08:50:57 +0200 Subject: [PATCH 2/8] feat: add possitility to generate smarts and kekule isomeric smiles We never documented toIsomericSmiles(boolean) so I didn't do a breaking change --- __tests__/__snapshots__/library.js.snap | 4 ++++ __tests__/molecule.js | 17 ++++++++++++++++- .../research/gwt/minimal/JSMolecule.java | 19 ++++++++++++++++--- types.d.ts | 9 +++++++-- 4 files changed, 43 insertions(+), 6 deletions(-) diff --git a/__tests__/__snapshots__/library.js.snap b/__tests__/__snapshots__/library.js.snap index 2199644b..eec44da4 100644 --- a/__tests__/__snapshots__/library.js.snap +++ b/__tests__/__snapshots__/library.js.snap @@ -296,6 +296,7 @@ exports[`prototype properties of Molecule 1`] = ` "toMolfile", "toMolfileV3", "toSVG", + "toSmarts", "toSmiles", "translateCoords", "validate", @@ -458,6 +459,9 @@ exports[`static properties of Molecule 1`] = ` "CANONIZER_TIE_BREAK_FREE_VALENCE_ATOMS", "FISCHER_PROJECTION_LIMIT", "FISCHER_PROJECTION_RING_LIMIT", + "MODE_CREATE_SMARTS", + "MODE_INCLUDE_MAPPING", + "MODE_KEKULIZED_OUTPUT", "STEREO_ANGLE_LIMIT", "VALIDATION_ERRORS_STEREO", "VALIDATION_ERROR_AMBIGUOUS_CONFIGURATION", diff --git a/__tests__/molecule.js b/__tests__/molecule.js index 8343bf83..4762696a 100644 --- a/__tests__/molecule.js +++ b/__tests__/molecule.js @@ -2,7 +2,7 @@ const fs = require('fs'); -const Molecule = require('../minimal').Molecule; +const { Molecule } = require('../minimal'); describe('from and to SMILES', () => { it.each(['C', 'COCOC', 'c1cc2cccc3c4cccc5cccc(c(c1)c23)c54'])( @@ -34,6 +34,21 @@ describe('from and to SMILES', () => { const mol = Molecule.fromSmiles(input); expect(mol.toIsomericSmiles()).toBe(output); }); + + it('smiles options', () => { + const mol = Molecule.fromSmiles('C1=CC=CC=C1'); + expect(mol.toSmiles()).toBe('C1(=CC=CC=C1)'); + expect(mol.toIsomericSmiles()).toBe('c1ccccc1'); + expect(mol.toIsomericSmiles(Molecule.MODE_KEKULIZED_OUTPUT)).toBe('C1=CC=CC=C1'); + expect(mol.toIsomericSmiles(Molecule.MODE_CREATE_SMARTS)).toBe('c1ccccc1'); + mol.setAtomMapNo(0, 1); + expect(mol.toIsomericSmiles(Molecule.MODE_INCLUDE_MAPPING)).toBe('c1cc[cH:1]cc1'); + + const fragment = Molecule.fromSmiles('C1=CC=CC=C1C'); + fragment.setFragment(true); + fragment.setAtomicNo(6, 1) + expect(fragment.toIsomericSmiles(Molecule.MODE_CREATE_SMARTS)).toBe('c1cc[c;!H0]cc1'); + }) }); describe('Molecule', () => { diff --git a/src/com/actelion/research/gwt/minimal/JSMolecule.java b/src/com/actelion/research/gwt/minimal/JSMolecule.java index be61a958..e4010aec 100644 --- a/src/com/actelion/research/gwt/minimal/JSMolecule.java +++ b/src/com/actelion/research/gwt/minimal/JSMolecule.java @@ -82,11 +82,17 @@ public native Object getOCL() }-*/; public String toSmiles() { + // we still allow to old code that do not care about stereo features and provide another SMILES return new SmilesCreator().generateSmiles(oclMolecule); } - public String toIsomericSmiles(boolean includeAtomMapping) { - return new IsomericSmilesCreator(oclMolecule, includeAtomMapping).getSmiles(); + public String toIsomericSmiles(int mode) { + return new IsomericSmilesCreator(oclMolecule, mode).getSmiles(); + } + + public String toSmarts() { + return new IsomericSmilesCreator(oclMolecule, IsomericSmilesCreator.MODE_CREATE_SMARTS) + .getSmiles(); } public String toMolfile() { @@ -293,7 +299,14 @@ public StereoMolecule getStereoMolecule() { return oclMolecule; } - // coming form Canonizer.java + // coming from IsomericSmilesCreator.java + public static final int MODE_CREATE_SMARTS = 1; + public static final int MODE_INCLUDE_MAPPING = 2; + public static final int MODE_KEKULIZED_OUTPUT = 4; // no lower case atom labels and single/double + // bonds to represent aromaticity + + + // coming from Canonizer.java public static final int CANONIZER_CREATE_SYMMETRY_RANK = 1; public static final int CANONIZER_CONSIDER_DIASTEREOTOPICITY = 2; public static final int CANONIZER_CONSIDER_ENANTIOTOPICITY = 4; diff --git a/types.d.ts b/types.d.ts index b0998879..7bf67701 100644 --- a/types.d.ts +++ b/types.d.ts @@ -93,6 +93,11 @@ export declare class Molecule { // based on JSMolecule.java you can do a regexp ".*static.* (int|long|double)(.*) = .*;" and replace with "$2: number;" + static MODE_CREATE_SMARTS = 1; + static MODE_INCLUDE_MAPPING = 2; + static MODE_KEKULIZED_OUTPUT = 4; // no lower case atom labels and single/double + // bonds to represent aromaticity + static CANONIZER_CREATE_SYMMETRY_RANK: number; static CANONIZER_CONSIDER_DIASTEREOTOPICITY: number; static CANONIZER_CONSIDER_ENANTIOTOPICITY: number; @@ -435,7 +440,7 @@ export declare class Molecule { */ getOCL(): any; - toSmiles(): string; + toSmiles(mode?: number): string; toIsomericSmiles(): string; @@ -1302,7 +1307,7 @@ export declare class Molecule { * @param mapNo * @param autoMapped */ - setAtomMapNo(atom: number, mapNo: number, autoMapped: boolean): void; + setAtomMapNo(atom: number, mapNo: number, autoMapped?: boolean): void; /** * Set atom to specific isotop or to have a natural isotop distribution From 9543039d8a91042bcc671740b40593e3daeb1580 Mon Sep 17 00:00:00 2001 From: Luc Patiny Date: Mon, 13 May 2024 15:03:29 +0200 Subject: [PATCH 3/8] chore: improve toIsomericSmiles method to allow options --- __tests__/molecule.js | 8 ++--- .../research/gwt/minimal/JSMolecule.java | 31 ++++++++++++++----- types.d.ts | 25 ++++++++++++--- 3 files changed, 47 insertions(+), 17 deletions(-) diff --git a/__tests__/molecule.js b/__tests__/molecule.js index 4762696a..a6014fb3 100644 --- a/__tests__/molecule.js +++ b/__tests__/molecule.js @@ -39,15 +39,15 @@ describe('from and to SMILES', () => { const mol = Molecule.fromSmiles('C1=CC=CC=C1'); expect(mol.toSmiles()).toBe('C1(=CC=CC=C1)'); expect(mol.toIsomericSmiles()).toBe('c1ccccc1'); - expect(mol.toIsomericSmiles(Molecule.MODE_KEKULIZED_OUTPUT)).toBe('C1=CC=CC=C1'); - expect(mol.toIsomericSmiles(Molecule.MODE_CREATE_SMARTS)).toBe('c1ccccc1'); + expect(mol.toIsomericSmiles({ kekulizedOutput: true })).toBe('C1=CC=CC=C1'); + expect(mol.toIsomericSmiles({ createSmarts: true })).toBe('c1ccccc1'); mol.setAtomMapNo(0, 1); - expect(mol.toIsomericSmiles(Molecule.MODE_INCLUDE_MAPPING)).toBe('c1cc[cH:1]cc1'); + expect(mol.toIsomericSmiles({ includeMapping: true })).toBe('c1cc[cH:1]cc1'); const fragment = Molecule.fromSmiles('C1=CC=CC=C1C'); fragment.setFragment(true); fragment.setAtomicNo(6, 1) - expect(fragment.toIsomericSmiles(Molecule.MODE_CREATE_SMARTS)).toBe('c1cc[c;!H0]cc1'); + expect(fragment.toIsomericSmiles({ createSmarts: true })).toBe('c1cc[c;!H0]cc1'); }) }); diff --git a/src/com/actelion/research/gwt/minimal/JSMolecule.java b/src/com/actelion/research/gwt/minimal/JSMolecule.java index e4010aec..0384c38f 100644 --- a/src/com/actelion/research/gwt/minimal/JSMolecule.java +++ b/src/com/actelion/research/gwt/minimal/JSMolecule.java @@ -86,7 +86,29 @@ public String toSmiles() { return new SmilesCreator().generateSmiles(oclMolecule); } - public String toIsomericSmiles(int mode) { + public native String toIsomericSmiles(JavaScriptObject options) + /*-{ + options = options || {} + var createSmarts = options.createSmarts === true; + var includeMapping = options.includeMapping === true; + var kekulizedOutput = options.kekulizedOutput === true; + console.log(createSmarts, includeMapping, kekulizedOutput); + return this.@com.actelion.research.gwt.minimal.JSMolecule::toIsomericSmilesInternal(ZZZ)(createSmarts, includeMapping, kekulizedOutput); + }-*/; + + @JsIgnore + public String toIsomericSmilesInternal(boolean createSmarts, boolean includeMapping, + boolean kekulizedOutput) { + int mode = 0; + if (createSmarts) { + mode |= IsomericSmilesCreator.MODE_CREATE_SMARTS; + } + if (includeMapping) { + mode |= IsomericSmilesCreator.MODE_INCLUDE_MAPPING; + } + if (kekulizedOutput) { + mode |= IsomericSmilesCreator.MODE_KEKULIZED_OUTPUT; + } return new IsomericSmilesCreator(oclMolecule, mode).getSmiles(); } @@ -299,13 +321,6 @@ public StereoMolecule getStereoMolecule() { return oclMolecule; } - // coming from IsomericSmilesCreator.java - public static final int MODE_CREATE_SMARTS = 1; - public static final int MODE_INCLUDE_MAPPING = 2; - public static final int MODE_KEKULIZED_OUTPUT = 4; // no lower case atom labels and single/double - // bonds to represent aromaticity - - // coming from Canonizer.java public static final int CANONIZER_CREATE_SYMMETRY_RANK = 1; public static final int CANONIZER_CONSIDER_DIASTEREOTOPICITY = 2; diff --git a/types.d.ts b/types.d.ts index 7bf67701..6081e9dc 100644 --- a/types.d.ts +++ b/types.d.ts @@ -61,6 +61,24 @@ export interface IHoseCodesOptions { type: 0 | 1; } +export interface ISmilesGeneratorOptions { + /** + * Whether to create SMILES with SMARTS capabilities. + * @default `false` + */ + createSmarts?: boolean; + /** + * Whether to include mapping information (atomMapNo) in the SMILES. + * @default `false` + */ + includeMapping?: boolean; + /** + * Should localisation of double bonds be attempted? + * @default `false` + */ + kekulizedOutput?: boolean; +} + export interface Rectangle { /** * X-coordinate of the top-left corner. @@ -93,10 +111,7 @@ export declare class Molecule { // based on JSMolecule.java you can do a regexp ".*static.* (int|long|double)(.*) = .*;" and replace with "$2: number;" - static MODE_CREATE_SMARTS = 1; - static MODE_INCLUDE_MAPPING = 2; - static MODE_KEKULIZED_OUTPUT = 4; // no lower case atom labels and single/double - // bonds to represent aromaticity + // bonds to represent aromaticity static CANONIZER_CREATE_SYMMETRY_RANK: number; static CANONIZER_CONSIDER_DIASTEREOTOPICITY: number; @@ -442,7 +457,7 @@ export declare class Molecule { toSmiles(mode?: number): string; - toIsomericSmiles(): string; + toIsomericSmiles(options?: ISmilesGeneratorOptions): string; /** * Returns a MDL Molfile V2000 string. From 8a46ddeaefed1b1c1c3d771fc11bfdb40218eaab Mon Sep 17 00:00:00 2001 From: Luc Patiny Date: Mon, 13 May 2024 15:10:07 +0200 Subject: [PATCH 4/8] chore: remove console.log --- src/com/actelion/research/gwt/minimal/JSMolecule.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/com/actelion/research/gwt/minimal/JSMolecule.java b/src/com/actelion/research/gwt/minimal/JSMolecule.java index 0384c38f..3964769b 100644 --- a/src/com/actelion/research/gwt/minimal/JSMolecule.java +++ b/src/com/actelion/research/gwt/minimal/JSMolecule.java @@ -92,7 +92,6 @@ public native String toIsomericSmiles(JavaScriptObject options) var createSmarts = options.createSmarts === true; var includeMapping = options.includeMapping === true; var kekulizedOutput = options.kekulizedOutput === true; - console.log(createSmarts, includeMapping, kekulizedOutput); return this.@com.actelion.research.gwt.minimal.JSMolecule::toIsomericSmilesInternal(ZZZ)(createSmarts, includeMapping, kekulizedOutput); }-*/; From b6841ed9931885d0f33965d7003a5d3bc5b3f676 Mon Sep 17 00:00:00 2001 From: Luc Patiny Date: Mon, 13 May 2024 15:12:58 +0200 Subject: [PATCH 5/8] test: fix snapshot --- __tests__/__snapshots__/library.js.snap | 3 --- 1 file changed, 3 deletions(-) diff --git a/__tests__/__snapshots__/library.js.snap b/__tests__/__snapshots__/library.js.snap index eec44da4..232671a9 100644 --- a/__tests__/__snapshots__/library.js.snap +++ b/__tests__/__snapshots__/library.js.snap @@ -459,9 +459,6 @@ exports[`static properties of Molecule 1`] = ` "CANONIZER_TIE_BREAK_FREE_VALENCE_ATOMS", "FISCHER_PROJECTION_LIMIT", "FISCHER_PROJECTION_RING_LIMIT", - "MODE_CREATE_SMARTS", - "MODE_INCLUDE_MAPPING", - "MODE_KEKULIZED_OUTPUT", "STEREO_ANGLE_LIMIT", "VALIDATION_ERRORS_STEREO", "VALIDATION_ERROR_AMBIGUOUS_CONFIGURATION", From 9cd73936b03ae2cbf1df200ac37f94b4ee6e1b60 Mon Sep 17 00:00:00 2001 From: Luc Patiny Date: Mon, 13 May 2024 15:15:05 +0200 Subject: [PATCH 6/8] chore: revert toSmiles mode in TS --- types.d.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/types.d.ts b/types.d.ts index 6081e9dc..c3afd95c 100644 --- a/types.d.ts +++ b/types.d.ts @@ -455,7 +455,7 @@ export declare class Molecule { */ getOCL(): any; - toSmiles(mode?: number): string; + toSmiles(): string; toIsomericSmiles(options?: ISmilesGeneratorOptions): string; From a4c23f64c2d2461ee6e4fe36f6c22f043e396a64 Mon Sep 17 00:00:00 2001 From: Luc Patiny Date: Mon, 13 May 2024 15:21:22 +0200 Subject: [PATCH 7/8] test: add toSmarts test --- __tests__/molecule.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/__tests__/molecule.js b/__tests__/molecule.js index a6014fb3..a53833f1 100644 --- a/__tests__/molecule.js +++ b/__tests__/molecule.js @@ -51,6 +51,13 @@ describe('from and to SMILES', () => { }) }); +it('smarts options', () => { + const fragment = Molecule.fromSmiles('C1=CC=CC=C1C'); + fragment.setFragment(true); + fragment.setAtomicNo(6, 1) + expect(fragment.toSmarts()).toBe('c1cc[c;!H0]cc1'); +}) + describe('Molecule', () => { it('medley', () => { const idcode = From 26c3c6bf1064f17a020fa6c2a31b39d28c734dfa Mon Sep 17 00:00:00 2001 From: Luc Patiny Date: Mon, 13 May 2024 15:22:53 +0200 Subject: [PATCH 8/8] chore: add toSmarts in TS --- types.d.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/types.d.ts b/types.d.ts index c3afd95c..fe1d2d8a 100644 --- a/types.d.ts +++ b/types.d.ts @@ -457,6 +457,8 @@ export declare class Molecule { toSmiles(): string; + toSmarts(): string; + toIsomericSmiles(options?: ISmilesGeneratorOptions): string; /**