From be7f2df2f6d6ad625c0e7c54c7b59c5b16fe9fab Mon Sep 17 00:00:00 2001 From: Luc Patiny Date: Fri, 17 Apr 2026 19:28:24 +0200 Subject: [PATCH 01/11] ci: replace documentationjs with typedoc workflow --- .github/workflows/documentationjs.yml | 21 --------------------- .github/workflows/typedoc.yml | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 21 deletions(-) delete mode 100644 .github/workflows/documentationjs.yml create mode 100644 .github/workflows/typedoc.yml diff --git a/.github/workflows/documentationjs.yml b/.github/workflows/documentationjs.yml deleted file mode 100644 index 0ae90e1..0000000 --- a/.github/workflows/documentationjs.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: Deploy documentation.js on GitHub pages - -on: - workflow_dispatch: - release: - types: [published] - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Build documentation - uses: zakodium/documentationjs-action@v1 - - name: Deploy to GitHub pages - uses: JamesIves/github-pages-deploy-action@releases/v4 - with: - token: ${{ secrets.BOT_TOKEN }} - branch: gh-pages - folder: docs - clean: true diff --git a/.github/workflows/typedoc.yml b/.github/workflows/typedoc.yml new file mode 100644 index 0000000..81c1ff6 --- /dev/null +++ b/.github/workflows/typedoc.yml @@ -0,0 +1,15 @@ +name: TypeDoc + +on: + workflow_dispatch: + release: + types: [published] + +jobs: + typedoc: + # Documentation: https://github.com/zakodium/workflows#typedoc + uses: zakodium/workflows/.github/workflows/typedoc.yml@typedoc-v1 + with: + entry: 'src/index.ts' + secrets: + github-token: ${{ secrets.BOT_TOKEN }} From 4bcfb996ab3fa757fdb566d0c209ffd1be1e6327 Mon Sep 17 00:00:00 2001 From: Luc Patiny Date: Fri, 17 Apr 2026 19:28:27 +0200 Subject: [PATCH 02/11] chore: ignore .claude and drop stale gitignore entries --- .gitignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index e4a63b2..3faf0bf 100644 --- a/.gitignore +++ b/.gitignore @@ -29,8 +29,8 @@ node_modules # webstorm generated files .idea -conrec.js -lib-esm lib .DS_Store + +.claude From a99b011070c364cec374c301a295d48764c99b3f Mon Sep 17 00:00:00 2001 From: Luc Patiny Date: Fri, 17 Apr 2026 19:28:30 +0200 Subject: [PATCH 03/11] chore: add .DS_Store to src/.npmignore --- src/.npmignore | 1 + 1 file changed, 1 insertion(+) diff --git a/src/.npmignore b/src/.npmignore index d7a3323..76cf446 100644 --- a/src/.npmignore +++ b/src/.npmignore @@ -1,2 +1,3 @@ __tests__ +.DS_Store .npmignore From 361ba7c5d50cc2168d36974f122b2609cca3b17a Mon Sep 17 00:00:00 2001 From: Luc Patiny Date: Fri, 17 Apr 2026 19:28:33 +0200 Subject: [PATCH 04/11] chore: migrate ESLint to flat config (v9) --- .eslintignore | 1 - .eslintrc.yml | 6 ------ eslint.config.js | 4 ++++ 3 files changed, 4 insertions(+), 7 deletions(-) delete mode 100644 .eslintignore delete mode 100644 .eslintrc.yml create mode 100644 eslint.config.js diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index b80e164..0000000 --- a/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -src/Conrec.js diff --git a/.eslintrc.yml b/.eslintrc.yml deleted file mode 100644 index cf10b61..0000000 --- a/.eslintrc.yml +++ /dev/null @@ -1,6 +0,0 @@ -extends: cheminfo-typescript -parserOptions: - sourceType: module -env: - jest: true - node: true diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..00cc43d --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,4 @@ +import { defineConfig, globalIgnores } from 'eslint/config'; +import cheminfo from 'eslint-config-cheminfo-typescript'; + +export default defineConfig(globalIgnores(['coverage', 'lib']), cheminfo); From 1b6496847c13db9cc675c9e1a567d05c10db4e75 Mon Sep 17 00:00:00 2001 From: Luc Patiny Date: Fri, 17 Apr 2026 19:28:40 +0200 Subject: [PATCH 05/11] chore: remove babel configuration --- .babelrc.json | 3 --- babel.config.js | 4 ---- 2 files changed, 7 deletions(-) delete mode 100644 .babelrc.json delete mode 100644 babel.config.js diff --git a/.babelrc.json b/.babelrc.json deleted file mode 100644 index 34bc6d2..0000000 --- a/.babelrc.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "plugins": ["@babel/plugin-transform-modules-commonjs"] -} diff --git a/babel.config.js b/babel.config.js deleted file mode 100644 index f5f790b..0000000 --- a/babel.config.js +++ /dev/null @@ -1,4 +0,0 @@ -module.exports = { - presets: ['@babel/preset-typescript'], - plugins: ['@babel/plugin-transform-modules-commonjs'], -}; From a379f1bfa96ed73ae3e30c957d98d6c51a110f23 Mon Sep 17 00:00:00 2001 From: Luc Patiny Date: Fri, 17 Apr 2026 19:28:43 +0200 Subject: [PATCH 06/11] chore: use @zakodium/tsconfig with single build config --- tsconfig.build.json | 5 +++++ tsconfig.cjs.json | 9 --------- tsconfig.esm.json | 7 ------- tsconfig.json | 11 ++++------- 4 files changed, 9 insertions(+), 23 deletions(-) create mode 100644 tsconfig.build.json delete mode 100644 tsconfig.cjs.json delete mode 100644 tsconfig.esm.json diff --git a/tsconfig.build.json b/tsconfig.build.json new file mode 100644 index 0000000..1fec47c --- /dev/null +++ b/tsconfig.build.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.json", + "include": ["src"], + "exclude": ["**/__tests__", "**/*.test.ts"] +} diff --git a/tsconfig.cjs.json b/tsconfig.cjs.json deleted file mode 100644 index 3b9e100..0000000 --- a/tsconfig.cjs.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "extends": "./tsconfig.json", - "compilerOptions": { - "module": "commonjs", - "declaration": true, - "declarationMap": true - }, - "exclude": ["./src/**/__tests__"] -} diff --git a/tsconfig.esm.json b/tsconfig.esm.json deleted file mode 100644 index 050b45d..0000000 --- a/tsconfig.esm.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "extends": "./tsconfig.cjs.json", - "compilerOptions": { - "module": "es2020", - "outDir": "lib-esm" - } -} diff --git a/tsconfig.json b/tsconfig.json index 321f97e..119b52a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,12 +1,9 @@ { + "extends": "@zakodium/tsconfig", "compilerOptions": { - "esModuleInterop": true, - "moduleResolution": "node", + "noUncheckedIndexedAccess": false, "outDir": "lib", - "skipLibCheck": true, - "sourceMap": true, - "strict": true, - "target": "es2020" + "types": ["node"] }, - "include": ["./src/**/*"] + "include": ["src", "benchmark", "vitest.config.ts"] } From 5ec501e2290bab00e3ea9c431ecdbd2c0dea7b78 Mon Sep 17 00:00:00 2001 From: Luc Patiny Date: Fri, 17 Apr 2026 19:28:46 +0200 Subject: [PATCH 07/11] test: add vitest config with full snapshot output --- vitest.config.ts | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 vitest.config.ts diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..7ed5e86 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,9 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + snapshotFormat: { + maxOutputLength: Number.MAX_SAFE_INTEGER, + }, + }, +}); From ac5082a675950e48ecd42de38be8e810d3d898db Mon Sep 17 00:00:00 2001 From: Luc Patiny Date: Sat, 18 Apr 2026 06:25:01 +0200 Subject: [PATCH 08/11] feat!: migrate package to ESM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convert to a pure ESM package: `"type": "module"`, `"exports"` field, single `lib/` output, `.ts` extensions on local imports, and split `type` imports. The `lib-esm` entry point is removed; use `lib` via the `exports` field. BREAKING CHANGE: package is now ESM-only; consumers using require() must migrate to import, or run Node.js ≥ 20.19 (or ≥ 22.12 / any 24.x), which supports require() of ESM. --- package.json | 47 ++++++++++++++++++--------------------- src/BasicContourDrawer.ts | 2 +- src/ContourBuilder.ts | 12 +++++----- src/ShapeContourDrawer.ts | 9 ++++---- src/calculateContour.ts | 27 ++++++++++++---------- src/index.ts | 8 +++---- 6 files changed, 53 insertions(+), 52 deletions(-) diff --git a/package.json b/package.json index 345d710..b9864d0 100644 --- a/package.json +++ b/package.json @@ -2,31 +2,30 @@ "name": "ml-conrec", "version": "5.0.3", "description": "JavaScript implementation of the CONREC contouring algorithm", - "main": "./lib/index.js", - "module": "./lib-esm/index.js", - "types": "./lib/index.d.ts", + "type": "module", + "exports": { + ".": "./lib/index.js" + }, "files": [ - "src", "lib", - "lib-esm" + "src" ], "scripts": { "check-types": "tsc --noEmit", - "clean": "rimraf lib lib-esm", + "clean": "rimraf lib", "eslint": "eslint src", "eslint-fix": "npm run eslint -- --fix", "prepack": "npm run tsc", "prettier": "prettier --check src", "prettier-write": "prettier --write src", - "test": "npm run test-only && npm run eslint && npm run prettier && npm run check-types", + "test": "npm run test-only && npm run check-types && npm run eslint && npm run prettier", "test-only": "vitest run --coverage", - "tsc": "npm run clean && npm run tsc-cjs && npm run tsc-esm", - "tsc-cjs": "tsc --project tsconfig.cjs.json", - "tsc-esm": "tsc --project tsconfig.esm.json" + "tsc": "npm run clean && npm run tsc-build", + "tsc-build": "tsc --project tsconfig.build.json" }, "repository": { "type": "git", - "url": "https://github.com/mljs/conrec.git" + "url": "git+https://github.com/mljs/conrec.git" }, "keywords": [ "algorithm", @@ -46,21 +45,19 @@ "url": "https://github.com/mljs/conrec/issues" }, "homepage": "https://github.com/mljs/conrec#readme", + "dependencies": { + "cheminfo-types": "^1.10.0" + }, "devDependencies": { - "@babel/plugin-transform-modules-commonjs": "^7.24.8", - "@babel/preset-typescript": "^7.24.7", - "@types/node": "^22.5.4", - "@vitest/coverage-v8": "^2.1.0", - "eslint": "^8.38.0", - "eslint-config-cheminfo-typescript": "^11.3.1", - "esm": "^3.2.25", + "@types/node": "^25.3.0", + "@vitest/coverage-v8": "^4.0.18", + "@zakodium/tsconfig": "^1.0.2", + "eslint": "^9.39.2", + "eslint-config-cheminfo-typescript": "^21.1.0", "jcampconverter": "^9.6.4", - "prettier": "^3.3.3", - "rimraf": "^6.0.1", - "typescript": "^5.6.2", - "vitest": "^2.1.0" - }, - "dependencies": { - "cheminfo-types": "^1.8.0" + "prettier": "^3.8.1", + "rimraf": "^6.1.3", + "typescript": "^5.9.3", + "vitest": "^4.0.18" } } diff --git a/src/BasicContourDrawer.ts b/src/BasicContourDrawer.ts index 905de11..c6f4576 100644 --- a/src/BasicContourDrawer.ts +++ b/src/BasicContourDrawer.ts @@ -7,7 +7,7 @@ export class BasicContourDrawer { private contour: BasicContour[]; private swapAxes: boolean; - constructor(levels: Readonly, swapAxes: boolean) { + constructor(levels: readonly number[], swapAxes: boolean) { this.contour = []; for (const level of levels) { this.contour.push({ diff --git a/src/ContourBuilder.ts b/src/ContourBuilder.ts index f02c0ae..5bce3f2 100644 --- a/src/ContourBuilder.ts +++ b/src/ContourBuilder.ts @@ -15,12 +15,12 @@ * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright + * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright + * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * * Neither the name of the nor the + * Neither the name of the nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * @@ -80,12 +80,12 @@ * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright + * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright + * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * * Neither the name of the nor the + * Neither the name of the nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * diff --git a/src/ShapeContourDrawer.ts b/src/ShapeContourDrawer.ts index 85a5a12..fe3d4e7 100644 --- a/src/ShapeContourDrawer.ts +++ b/src/ShapeContourDrawer.ts @@ -16,12 +16,12 @@ * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright + * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright + * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * * Neither the name of the nor the + * Neither the name of the nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * @@ -66,7 +66,8 @@ * MODIFICATIONS. */ -import { ContourBuilder, Point, SequenceNode } from './ContourBuilder'; +import type { Point, SequenceNode } from './ContourBuilder.ts'; +import { ContourBuilder } from './ContourBuilder.ts'; export interface ShapeContour { level: number; diff --git a/src/calculateContour.ts b/src/calculateContour.ts index d43d7b8..69be7c8 100644 --- a/src/calculateContour.ts +++ b/src/calculateContour.ts @@ -1,7 +1,7 @@ // https://github.com/jasondavies/conrec.js -import { NumberArray, NumberMatrix } from 'cheminfo-types'; +import type { NumberArray, NumberMatrix } from 'cheminfo-types'; -import { ContourDrawer } from '.'; +import type { ContourDrawer } from './index.ts'; // Changes have been done by MLJS team @@ -21,12 +21,12 @@ import { ContourDrawer } from '.'; * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: - * * Redistributions of source code must retain the above copyright + * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above copyright + * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. - * * Neither the name of the nor the + * Neither the name of the nor the * names of its contributors may be used to endorse or promote products * derived from this software without specific prior written permission. * @@ -95,7 +95,6 @@ interface CalculateContourOptions { * * Any number of contour levels may be specified but they must be in order of * increasing value. - * * @private * @param matrix - matrix of data to contour * @@ -104,9 +103,10 @@ interface CalculateContourOptions { * @param x - data matrix column coordinates * @param y - data matrix row coordinates * @param z - contour levels in increasing order. - * @param contourDrawer object that implements contourDraw for drawing contour. Defaults to a + * @param contourDrawer - object that implements contourDraw for drawing contour. Defaults to a * custom "contour builder", which populates the * contours property. + * @param options - additional calculation options (timeout and index bounds). * @returns Whether contour generation had to stop early because it reached the timeout */ export function calculateContour( @@ -135,7 +135,6 @@ export function calculateContour( const start = Date.now(); - /** private */ function xsect(p1: number, p2: number) { return (h[p2] * xh[p1] - h[p1] * xh[p2]) / (h[p2] - h[p1]); } @@ -147,10 +146,10 @@ export function calculateContour( let m1; let m2; let m3; - let x1 = 0.0; - let x2 = 0.0; - let y1 = 0.0; - let y2 = 0.0; + let x1 = 0; + let x2 = 0; + let y1 = 0; + let y2 = 0; // The indexing of im and jm should be noted as it has to start from zero // unlike the fortran counter part @@ -208,6 +207,10 @@ export function calculateContour( const dmin = Math.min(min1, min2); const dmax = Math.max(max1, max2); // Ternary operator is now much slower: https://jsbench.me/d5l0dh502g + + //const dmin = (min1 + min2 - Math.abs(min1 - min2)) / 2; + //const dmax = (max1 + max2 + Math.abs(max1 - max2)) / 2; + //let dmin = min1 > min2 ? min2 : min1; //let dmax = max1 > max2 ? max1 : max2; if (dmax >= z0 && dmin <= znc1) { diff --git a/src/index.ts b/src/index.ts index 9fc3a25..0500f47 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,8 +1,8 @@ -import { NumberArray, NumberMatrix } from 'cheminfo-types'; +import type { NumberArray, NumberMatrix } from 'cheminfo-types'; -import { BasicContourDrawer } from './BasicContourDrawer'; -import { ShapeContourDrawer } from './ShapeContourDrawer'; -import { calculateContour } from './calculateContour'; +import { BasicContourDrawer } from './BasicContourDrawer.ts'; +import { ShapeContourDrawer } from './ShapeContourDrawer.ts'; +import { calculateContour } from './calculateContour.ts'; interface ConrecOptions { xs?: Readonly; From 2f54aff9b88a110be7e9f266d85560193d71bce4 Mon Sep 17 00:00:00 2001 From: Luc Patiny Date: Fri, 17 Apr 2026 19:29:46 +0200 Subject: [PATCH 09/11] test: migrate to flat vitest structure with node: imports --- .../__snapshots__/conrec.test.ts.snap | 4 +- src/__tests__/conrec.test.ts | 169 +++++++++--------- src/__tests__/shape.test.ts | 140 ++++++++------- 3 files changed, 162 insertions(+), 151 deletions(-) diff --git a/src/__tests__/__snapshots__/conrec.test.ts.snap b/src/__tests__/__snapshots__/conrec.test.ts.snap index 62b98a5..098caa8 100644 --- a/src/__tests__/__snapshots__/conrec.test.ts.snap +++ b/src/__tests__/__snapshots__/conrec.test.ts.snap @@ -1,6 +1,6 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html -exports[`conrec basic test > auto select levels 1`] = ` +exports[`auto select levels 1`] = ` [ 167.3376920421198, 182.66230795788022, @@ -37,7 +37,7 @@ exports[`conrec basic test > auto select levels 1`] = ` ] `; -exports[`conrec basic test > auto select levels with swapAxes 1`] = ` +exports[`auto select levels with swapAxes 1`] = ` [ 182.66230795788022, 167.3376920421198, diff --git a/src/__tests__/conrec.test.ts b/src/__tests__/conrec.test.ts index 80d33d5..445accc 100644 --- a/src/__tests__/conrec.test.ts +++ b/src/__tests__/conrec.test.ts @@ -1,101 +1,108 @@ -import fs from 'fs'; +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; -import { NumberMatrix } from 'cheminfo-types'; +import type { NumberMatrix } from 'cheminfo-types'; import { convert } from 'jcampconverter'; -import { describe, it, expect } from 'vitest'; +import { expect, test } from 'vitest'; -import { Conrec } from '..'; +import { Conrec } from '../index.ts'; -const data = fs.readFileSync(`${__dirname}/data/zhmbc_0.jdx`, 'utf8'); +const data = readFileSync( + join(import.meta.dirname, 'data/zhmbc_0.jdx'), + 'utf8', +); const parsed = convert(data, { noContour: true }).flatten[0]; const matrix: NumberMatrix = parsed.minMax?.z || []; -describe('conrec basic test', () => { - it('no result because level too far', () => { - const conrec = new Conrec(matrix); - const { contours, timeout } = conrec.drawContour({ - contourDrawer: 'basic', - levels: [-1000000000, 1000000000], - timeout: 10000, - }); - expect(timeout).toBeFalsy(); - expect(contours).toStrictEqual([ - { lines: [], zValue: -1000000000 }, - { lines: [], zValue: 1000000000 }, - ]); + +test('no result because level too far', () => { + const conrec = new Conrec(matrix); + const { contours, timeout } = conrec.drawContour({ + contourDrawer: 'basic', + levels: [-1000000000, 1000000000], + timeout: 10000, }); - it('2 specified levels', () => { - const conrec = new Conrec(matrix); - const { contours, timeout } = conrec.drawContour({ - contourDrawer: 'basic', - levels: [-100000, 100000], - timeout: 10000, - }); - expect(timeout).toBeFalsy(); - expect(contours).toHaveLength(2); - expect(contours[0].lines).toHaveLength(36864); - expect(contours[1].lines).toHaveLength(119720); + expect(timeout).toBe(false); + expect(contours).toStrictEqual([ + { lines: [], zValue: -1000000000 }, + { lines: [], zValue: 1000000000 }, + ]); +}); + +test('2 specified levels', () => { + const conrec = new Conrec(matrix); + const { contours, timeout } = conrec.drawContour({ + contourDrawer: 'basic', + levels: [-100000, 100000], + timeout: 10000, }); - it('no levels', () => { - const conrec = new Conrec(matrix); - const start = Date.now(); - const { contours, timeout } = conrec.drawContour({ - contourDrawer: 'basic', - levels: [], - timeout: 10000, - }); - expect(Date.now() - start).toBeLessThan(25); - expect(timeout).toBeFalsy(); - expect(contours).toHaveLength(0); + expect(timeout).toBe(false); + expect(contours).toHaveLength(2); + expect(contours[0].lines).toHaveLength(36864); + expect(contours[1].lines).toHaveLength(119720); +}); + +test('no levels', () => { + const conrec = new Conrec(matrix); + const start = Date.now(); + const { contours, timeout } = conrec.drawContour({ + contourDrawer: 'basic', + levels: [], + timeout: 10000, }); - it('auto select levels', () => { - const conrec = new Conrec(matrix); - const { contours, timeout } = conrec.drawContour({ - contourDrawer: 'basic', - nbLevels: 10, - timeout: 10000, - }); + expect(Date.now() - start).toBeLessThan(25); + expect(timeout).toBe(false); + expect(contours).toHaveLength(0); +}); - expect(timeout).toBeFalsy(); - expect(contours).toHaveLength(10); - expect(contours[0].lines).toHaveLength(0); - expect(contours[1].lines).toHaveLength(4984); - expect(contours[8].lines).toHaveLength(32); - expect(contours[8].lines).toMatchSnapshot(); - expect(contours[9].lines).toHaveLength(0); +test('auto select levels', () => { + const conrec = new Conrec(matrix); + const { contours, timeout } = conrec.drawContour({ + contourDrawer: 'basic', + nbLevels: 10, + timeout: 10000, }); - it('auto select levels with swapAxes', () => { - const conrec = new Conrec(matrix, { swapAxes: true }); - const { contours, timeout } = conrec.drawContour({ - contourDrawer: 'basic', - nbLevels: 10, - timeout: 10000, - }); + expect(timeout).toBe(false); + expect(contours).toHaveLength(10); + expect(contours[0].lines).toHaveLength(0); + expect(contours[1].lines).toHaveLength(4984); + expect(contours[8].lines).toHaveLength(32); + expect(contours[8].lines).toMatchSnapshot(); + expect(contours[9].lines).toHaveLength(0); +}); - expect(timeout).toBeFalsy(); - expect(contours).toHaveLength(10); - expect(contours[0].lines).toHaveLength(0); - expect(contours[1].lines).toHaveLength(4984); - expect(contours[8].lines).toHaveLength(32); - expect(contours[8].lines).toMatchSnapshot(); - expect(contours[9].lines).toHaveLength(0); +test('auto select levels with swapAxes', () => { + const conrec = new Conrec(matrix, { swapAxes: true }); + const { contours, timeout } = conrec.drawContour({ + contourDrawer: 'basic', + nbLevels: 10, + timeout: 10000, }); - it('return available contours within 100ms', () => { - const conrec = new Conrec(matrix); - const { contours, timeout } = conrec.drawContour({ - contourDrawer: 'basic', - nbLevels: 10, - timeout: 10, - }); - expect(timeout).toBeTruthy(); - expect(contours).toHaveLength(10); - expect(contours[0].lines).toHaveLength(0); - expect(contours[1].lines.length).toBeLessThan(2000); - expect(contours[8].lines).toHaveLength(0); - expect(contours[9].lines).toHaveLength(0); + expect(timeout).toBe(false); + expect(contours).toHaveLength(10); + expect(contours[0].lines).toHaveLength(0); + expect(contours[1].lines).toHaveLength(4984); + expect(contours[8].lines).toHaveLength(32); + expect(contours[8].lines).toMatchSnapshot(); + expect(contours[9].lines).toHaveLength(0); +}); + +test('return available contours within 100ms', () => { + const conrec = new Conrec(matrix); + const { contours, timeout } = conrec.drawContour({ + contourDrawer: 'basic', + nbLevels: 10, + timeout: 10, }); + + expect(timeout).toBe(true); + expect(contours).toHaveLength(10); + expect(contours[0].lines).toHaveLength(0); + expect(contours[1].lines.length).toBeLessThan(2000); + expect(contours[8].lines).toHaveLength(0); + expect(contours[9].lines).toHaveLength(0); }); diff --git a/src/__tests__/shape.test.ts b/src/__tests__/shape.test.ts index b7946c2..4aa9e3d 100644 --- a/src/__tests__/shape.test.ts +++ b/src/__tests__/shape.test.ts @@ -1,93 +1,97 @@ -import { describe, it, expect } from 'vitest'; +import { expect, test } from 'vitest'; -import { Conrec } from '..'; +import { Conrec } from '../index.ts'; -describe('shape', () => { - it('square', () => { - const matrix = parse(` - 00000 - 01110 - 01110 - 01110 - 00000 - `); +test('square', () => { + const matrix = parse(` + 00000 + 01110 + 01110 + 01110 + 00000 + `); - const conrec = new Conrec(matrix); - const { contours, timeout } = conrec.drawContour({ - contourDrawer: 'shape', - levels: [0.5], - }); - expect(timeout).toBeFalsy(); - expect(contours[0].lines).toHaveLength(25); + const conrec = new Conrec(matrix); + const { contours, timeout } = conrec.drawContour({ + contourDrawer: 'shape', + levels: [0.5], }); - it('2 squares', () => { - const matrix = parse(` - 000000000 - 011101110 - 011101110 - 011101110 - 000000000 - `); + expect(timeout).toBe(false); + expect(contours[0].lines).toHaveLength(25); +}); - const conrec = new Conrec(matrix); - const { contours, timeout } = conrec.drawContour({ - contourDrawer: 'shape', - levels: [0.5], - }); +test('2 squares', () => { + const matrix = parse(` + 000000000 + 011101110 + 011101110 + 011101110 + 000000000 + `); - expect(timeout).toBeFalsy(); - expect(contours[0].lines).toHaveLength(25); + const conrec = new Conrec(matrix); + const { contours, timeout } = conrec.drawContour({ + contourDrawer: 'shape', + levels: [0.5], }); - it('2 diagonal squares', () => { - const matrix = parse(` - 000000000 - 011100000 - 011100000 - 011100000 - 000011100 - 000011100 - 000011100 - 000000000 - `); + expect(timeout).toBe(false); + expect(contours[0].lines).toHaveLength(25); +}); + +test('2 diagonal squares', () => { + const matrix = parse(` + 000000000 + 011100000 + 011100000 + 011100000 + 000011100 + 000011100 + 000011100 + 000000000 + `); - const conrec = new Conrec(matrix); - const { contours, timeout } = conrec.drawContour({ - contourDrawer: 'shape', - levels: [0.5], - }); - expect(timeout).toBeFalsy(); - expect(contours[0].lines).toHaveLength(49); + const conrec = new Conrec(matrix); + const { contours, timeout } = conrec.drawContour({ + contourDrawer: 'shape', + levels: [0.5], }); - it('square float matrix', () => { - const matrix: Float64Array[] = [ - Float64Array.from([0, 0, 0.1, 0, 0]), - Float64Array.from([0, 0.9, 1, 1, 0]), - Float64Array.from([0, 1, 1, 1, 0]), - Float64Array.from([0, 1, 1, 0.8, 0]), - Float64Array.from([0, 0, 0, 0, 0.1]), - ]; - const conrec = new Conrec(matrix); - const { contours, timeout } = conrec.drawContour({ - contourDrawer: 'shape', - levels: [0.5], - }); - expect(timeout).toBeFalsy(); - expect(contours[0].lines).toHaveLength(29); + expect(timeout).toBe(false); + expect(contours[0].lines).toHaveLength(49); +}); + +test('square float matrix', () => { + const matrix: Float64Array[] = [ + Float64Array.from([0, 0, 0.1, 0, 0]), + Float64Array.from([0, 0.9, 1, 1, 0]), + Float64Array.from([0, 1, 1, 1, 0]), + Float64Array.from([0, 1, 1, 0.8, 0]), + Float64Array.from([0, 0, 0, 0, 0.1]), + ]; + + const conrec = new Conrec(matrix); + const { contours, timeout } = conrec.drawContour({ + contourDrawer: 'shape', + levels: [0.5], }); + + expect(timeout).toBe(false); + expect(contours[0].lines).toHaveLength(29); }); function parse(string: string) { const lines = string .split(/\r?\n/) .map((line) => line.trim()) - .filter((line) => line); + .filter(Boolean); const matrix: number[][] = []; for (const line of lines) { - matrix.push(line.split('').map((value: string) => parseInt(value, 10))); + matrix.push( + line.split('').map((value: string) => Number.parseInt(value, 10)), + ); } return matrix; } From 16a44caed341377de364a52ac42891e69cec9e5e Mon Sep 17 00:00:00 2001 From: Luc Patiny Date: Fri, 17 Apr 2026 19:29:48 +0200 Subject: [PATCH 10/11] chore: migrate benchmark script to TypeScript --- benchmark/contour.mjs | 58 ------------------------------------------- benchmark/contour.ts | 54 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 58 deletions(-) delete mode 100644 benchmark/contour.mjs create mode 100644 benchmark/contour.ts diff --git a/benchmark/contour.mjs b/benchmark/contour.mjs deleted file mode 100644 index fd07504..0000000 --- a/benchmark/contour.mjs +++ /dev/null @@ -1,58 +0,0 @@ -// node --cpu-prof benchmark/contour.mjs - -import { readFileSync } from 'fs'; -import { join } from 'path' - -import { convert } from 'jcampconverter'; - -import { Conrec } from '../lib/index.js'; - -const __dirname = new URL('.', import.meta.url).pathname; - -const data = readFileSync(join(__dirname, '../src/__tests__/data/zhmbc_0.jdx'), 'utf8'); -const parsed = convert(data, { noContour: true }).flatten[0]; - -for (let i = 0; i < parsed.minMax.z.length; i++) { - parsed.minMax.z[i] = Float64Array.from(parsed.minMax.z[i]) -} - -console.log('Size: ', parsed.minMax.z[0].length, 'x', parsed.minMax.z.length); - -const conrec = new Conrec(parsed.minMax.z); - -let levels = []; -for (let level = -1e4; level <= 1e4; level += 2e2) { - levels.push(level); -} - -console.log(`We calculate ${levels.length} levels close to zero to be close to noise`); - -let result; - -if (true) { - console.time('basic'); - - result = (conrec.drawContour({ - contourDrawer: 'basic', - levels, - timeout: 100000, - })); - console.log(result.contours.length) - console.timeEnd('basic'); - const totalNumberContours = result.contours.reduce((acc, contour) => acc + contour.lines.length, 0); - console.log({ totalNumberContours }) -} else { - console.time('shape'); - result = conrec.drawContour({ - contourDrawer: 'shape', - levels, - timeout: 1000000, - }); - console.timeEnd('shape'); - // console.log(result) - console.log(result.contours.length) - const totalNumberContours = result.contours.reduce((acc, contour) => acc + contour.lines.length, 0); - console.log({ totalNumberContours }) - -} - diff --git a/benchmark/contour.ts b/benchmark/contour.ts new file mode 100644 index 0000000..3023ae2 --- /dev/null +++ b/benchmark/contour.ts @@ -0,0 +1,54 @@ +// node --cpu-prof benchmark/contour.ts + +import { readFileSync } from 'node:fs'; +import { join } from 'node:path'; + +import type { NumberMatrix } from 'cheminfo-types'; +import { convert } from 'jcampconverter'; + +import { Conrec } from '../src/index.ts'; + +const data = readFileSync( + join(import.meta.dirname, '../src/__tests__/data/zhmbc_0.jdx'), + 'utf8', +); +const parsed = convert(data, { noContour: true }).flatten[0]; + +const rawMatrix = parsed.minMax?.z; +if (!rawMatrix) { + throw new Error('Failed to parse matrix from JCAMP file'); +} + +const matrix: NumberMatrix = rawMatrix.map((row) => Float64Array.from(row)); + +// eslint-disable-next-line no-console +console.log('Size: ', matrix[0].length, 'x', matrix.length); + +const conrec = new Conrec(matrix); + +const levels: number[] = []; +for (let level = -1e4; level <= 1e4; level += 2e2) { + levels.push(level); +} + +// eslint-disable-next-line no-console +console.log( + `We calculate ${levels.length} levels close to zero to be close to noise`, +); + +console.time('basic'); + +const result = conrec.drawContour({ + contourDrawer: 'basic', + levels, + timeout: 100000, +}); +// eslint-disable-next-line no-console +console.log(result.contours.length); +console.timeEnd('basic'); +const totalNumberContours = result.contours.reduce( + (acc, contour) => acc + contour.lines.length, + 0, +); +// eslint-disable-next-line no-console +console.log({ totalNumberContours }); From 9ebce1b5572364f6388dc2c72afcf09d153a93ba Mon Sep 17 00:00:00 2001 From: Luc Patiny Date: Tue, 21 Apr 2026 10:20:23 +0200 Subject: [PATCH 11/11] chore: remove outdated comment --- src/calculateContour.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/calculateContour.ts b/src/calculateContour.ts index 69be7c8..a8f07b1 100644 --- a/src/calculateContour.ts +++ b/src/calculateContour.ts @@ -206,13 +206,7 @@ export function calculateContour( } const dmin = Math.min(min1, min2); const dmax = Math.max(max1, max2); - // Ternary operator is now much slower: https://jsbench.me/d5l0dh502g - //const dmin = (min1 + min2 - Math.abs(min1 - min2)) / 2; - //const dmax = (max1 + max2 + Math.abs(max1 - max2)) / 2; - - //let dmin = min1 > min2 ? min2 : min1; - //let dmax = max1 > max2 ? max1 : max2; if (dmax >= z0 && dmin <= znc1) { for (let k = 0; k < nc; k++) { if (z[k] >= dmin && z[k] <= dmax) {