Skip to content

Commit ec9318f

Browse files
jobo322targos
authored andcommitted
refactor: change exposed methods and accepted types
refactor!: treeSimilarity now only accepts Tree objects refactor!: createTree expects an object {x: [],y: []}
1 parent 591087b commit ec9318f

File tree

10 files changed

+144
-112
lines changed

10 files changed

+144
-112
lines changed

README.md

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,21 @@ Compares two spectra using a tree similarity.
1515
```js
1616
import { createTree, treeSimilarity } from 'ml-tree-similarity';
1717

18-
const a = [[1, 2, 3, 4, 5, 6, 7], [0.3, 0.7, 4, 0.3, 0.2, 5, 0.3]];
19-
const b = [[1, 2, 3, 4, 5, 6, 7], [0.3, 4, 0.7, 0.3, 5, 0.2, 0.3]];
18+
const a = {
19+
x: [1, 2, 3, 4, 5, 6, 7],
20+
y: [0.3, 0.7, 4, 0.3, 0.2, 5, 0.3],
21+
};
22+
const b = {
23+
x: [1, 2, 3, 4, 5, 6, 7],
24+
y: [0.3, 4, 0.7, 0.3, 5, 0.2, 0.3],
25+
};
2026

2127
// create a tree
2228
const options = { from: 1, to: 7 };
23-
const A = createTree(a, options);
29+
const aTree = createTree(a, options);
30+
const bTree = createTree(b, options);
2431

25-
// a pre-calculated tree is also a valid input
26-
const ans = treeSimilarity(A, b, options);
32+
const ans = treeSimilarity(aTree, bTree, options);
2733
```
2834

2935
## [API Documentation](https://mljs.github.io/tree-similarity/)

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040
},
4141
"dependencies": {
4242
"binary-search": "^1.3.6",
43+
"cheminfo-types": "^1.7.2",
4344
"num-sort": "^3.0.0"
4445
}
4546
}

src/__tests__/createTree.test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ describe('simple trees', () => {
66
it('two peaks, same height', () => {
77
let x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
88
let y = [0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0];
9-
let tree = createTree([x, y]);
9+
let tree = createTree({ x, y });
1010

1111
expect(tree.center).toBe(5);
1212
expect(tree.sum).toBe(2);
@@ -34,7 +34,7 @@ describe('simple trees', () => {
3434
y[20] = 20;
3535
y[80] = 20;
3636

37-
let tree = createTree([x, y]);
37+
let tree = createTree({ x, y });
3838

3939
expect(tree.center).toBe(50);
4040
expect(tree.sum).toBe(40);

src/__tests__/tree.test.js

Lines changed: 13 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,21 @@
11
import { describe, it, expect } from 'vitest';
22

3-
import { treeSimilarity, getFunction } from '..';
3+
import { createTree, treeSimilarity } from '../index';
44

5-
let a = [
6-
[1, 2, 3, 4, 5, 6, 7],
7-
[0.3, 0.7, 4, 0.3, 0.2, 5, 0.3],
8-
];
9-
let b = [
10-
[1, 2, 3, 4, 5, 6, 7],
11-
[0.3, 4, 0.7, 0.3, 5, 0.2, 0.3],
12-
];
5+
const a = {
6+
x: [1, 2, 3, 4, 5, 6, 7],
7+
y: [0.3, 0.7, 4, 0.3, 0.2, 5, 0.3],
8+
};
9+
const b = {
10+
x: [1, 2, 3, 4, 5, 6, 7],
11+
y: [0.3, 4, 0.7, 0.3, 5, 0.2, 0.3],
12+
};
1313

1414
describe('Tree similarity', () => {
1515
it('should work with two arrays', () => {
16-
expect(treeSimilarity(a, b)).toBeCloseTo(0.653354, 4);
17-
});
18-
19-
it('should currify the options', () => {
20-
let options = {
21-
alpha: 0.4,
22-
beta: 0.5,
23-
gamma: 0.001,
24-
};
25-
let func = getFunction(options);
26-
expect(func(a, b)).toBe(treeSimilarity(a, b, options));
16+
expect(treeSimilarity(createTree(a), createTree(b))).toBeCloseTo(
17+
0.653354,
18+
4,
19+
);
2720
});
2821
});
Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
import { describe, it, expect } from 'vitest';
22

33
import { createTree } from '../createTree';
4-
import { getSimilarity } from '../getSimilarity';
4+
import { treeSimilarity } from '../treeSimilarity';
55

66
describe('simple trees', () => {
77
it('same tree', () => {
88
let x = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
99
let y = [0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0];
10-
let tree1 = createTree([x, y]);
11-
let tree2 = createTree([x, y]);
10+
let tree1 = createTree({ x, y });
11+
let tree2 = createTree({ x, y });
1212

13-
expect(getSimilarity(tree1, tree2, { beta: 1 })).toBe(1);
13+
expect(treeSimilarity(tree1, tree2, { beta: 1 })).toBe(1);
1414
});
1515
});

src/createTree.js

Lines changed: 25 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,53 +1,50 @@
11
import binarySearch from 'binary-search';
22
import { numberSortAscending } from 'num-sort';
33

4+
/**
5+
* @typedef {import("../types").Tree} Tree
6+
* @typedef {import("../types").CreateTreeOptions} CreateTreeOptions
7+
* @typedef {import("../types").Spectrum} Spectrum
8+
*/
9+
410
/**
511
* Function that creates the tree
6-
* @param {Array<Array<number>>} spectrum
7-
* @param {object} [options]
8-
* @return {Tree|null}
9-
* left and right have the same structure than the parent,
10-
* or are null if they are leaves
12+
* @param {Spectrum} spectrum
13+
* @param {CreateTreeOptions} [options]
14+
* @return { Tree | null }
1115
*/
1216
export function createTree(spectrum, options = {}) {
13-
const X = spectrum[0];
17+
const { x, y } = spectrum;
1418
const {
1519
minWindow = 0.16,
1620
threshold = 0.01,
17-
from = X[0],
18-
to = X[X.length - 1],
21+
from = x[0],
22+
to = x[x.length - 1],
1923
} = options;
2024

21-
return mainCreateTree(
22-
spectrum[0],
23-
spectrum[1],
24-
from,
25-
to,
26-
minWindow,
27-
threshold,
28-
);
25+
return mainCreateTree(x, y, from, to, minWindow, threshold);
2926
}
3027

31-
function mainCreateTree(X, Y, from, to, minWindow, threshold) {
28+
function mainCreateTree(x, y, from, to, minWindow, threshold) {
3229
if (to - from < minWindow) {
3330
return null;
3431
}
3532

3633
// search first point
37-
let start = binarySearch(X, from, numberSortAscending);
34+
let start = binarySearch(x, from, numberSortAscending);
3835
if (start < 0) {
3936
start = ~start;
4037
}
4138

4239
// stop at last point
4340
let sum = 0;
4441
let center = 0;
45-
for (let i = start; i < X.length; i++) {
46-
if (X[i] >= to) {
42+
for (let i = start; i < x.length; i++) {
43+
if (x[i] >= to) {
4744
break;
4845
}
49-
sum += Y[i];
50-
center += X[i] * Y[i];
46+
sum += y[i];
47+
center += x[i] * y[i];
5148
}
5249

5350
if (sum < threshold) {
@@ -59,24 +56,15 @@ function mainCreateTree(X, Y, from, to, minWindow, threshold) {
5956
return null;
6057
}
6158
if (center - from < minWindow / 4) {
62-
return mainCreateTree(X, Y, center, to, minWindow, threshold);
59+
return mainCreateTree(x, y, center, to, minWindow, threshold);
6360
} else if (to - center < minWindow / 4) {
64-
return mainCreateTree(X, Y, from, center, minWindow, threshold);
61+
return mainCreateTree(x, y, from, center, minWindow, threshold);
6562
} else {
66-
return new Tree(
63+
return {
6764
sum,
6865
center,
69-
mainCreateTree(X, Y, from, center, minWindow, threshold),
70-
mainCreateTree(X, Y, center, to, minWindow, threshold),
71-
);
72-
}
73-
}
74-
75-
class Tree {
76-
constructor(sum, center, left, right) {
77-
this.sum = sum;
78-
this.center = center;
79-
this.left = left;
80-
this.right = right;
66+
left: mainCreateTree(x, y, from, center, minWindow, threshold),
67+
right: mainCreateTree(x, y, center, to, minWindow, threshold),
68+
};
8169
}
8270
}

src/getSimilarity.js

Lines changed: 0 additions & 34 deletions
This file was deleted.

src/index.js

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,2 @@
1-
import { getSimilarity } from './getSimilarity';
2-
1+
export { treeSimilarity } from './treeSimilarity';
32
export { createTree } from './createTree';
4-
5-
export function treeSimilarity(A, B, options = {}) {
6-
return getSimilarity(A, B, options);
7-
}
8-
9-
export function getFunction(options = {}) {
10-
return (A, B) => getSimilarity(A, B, options);
11-
}

src/treeSimilarity.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
/**
2+
* @typedef {import("../types").Tree} Tree
3+
* @typedef {import("../types").TreeSimilarityOptions} TreeSimilarityOptions
4+
*/
5+
/**
6+
* Similarity between two nodes
7+
* @param {Tree | null} a - tree A node
8+
* @param {Tree | null} b - tree B node
9+
* @param {TreeSimilarityOptions} [options]
10+
* @return {number} similarity measure between tree nodes
11+
*/
12+
export function treeSimilarity(a, b, options = {}) {
13+
const { alpha = 0.1, beta = 0.33, gamma = 0.001 } = options;
14+
15+
if (a === null || b === null) {
16+
return 0;
17+
}
18+
19+
if (!isTree(a) || !isTree(b)) {
20+
throw new Error('tree similarity expects tree as inputs');
21+
}
22+
23+
if (a.sum === 0 && b.sum === 0) {
24+
return 1;
25+
}
26+
27+
const C =
28+
(alpha * Math.min(a.sum, b.sum)) / Math.max(a.sum, b.sum) +
29+
(1 - alpha) * Math.exp(-gamma * Math.abs(a.center - b.center));
30+
31+
return (
32+
beta * C +
33+
((1 - beta) *
34+
(treeSimilarity(a.left, b.left, options) +
35+
treeSimilarity(a.right, b.right, options))) /
36+
2
37+
);
38+
}
39+
40+
function isTree(a) {
41+
return ['sum', 'center', 'left', 'right'].every((key) => key in a);
42+
}

types.d.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { NumberArray } from "cheminfo-types";
2+
3+
export interface Tree {
4+
sum: number;
5+
center: number;
6+
/**
7+
* left and right have the same structure than the parent,
8+
* or are null if they are leaves
9+
*/
10+
left: Tree | null;
11+
right: Tree | null;
12+
}
13+
14+
export interface CreateTreeOptions {
15+
/**
16+
* low limit of the tree
17+
* @default x[0]
18+
*/
19+
from?: number
20+
/**
21+
* high limit of the tree
22+
* @default x.at(-1)
23+
*/
24+
to?: number
25+
/**
26+
* minimal sum value to accept a node
27+
* @default 0.01
28+
*/
29+
threshold?: number;
30+
/**
31+
* minimal window width to create a node
32+
* @default 0.16
33+
*/
34+
minWindow?: number
35+
}
36+
37+
export interface TreeSimilarityOptions {
38+
alpha?: number;
39+
beta?: number;
40+
gamma?: number;
41+
}
42+
export interface Spectrum {
43+
x: NumberArray;
44+
y: NumberArray;
45+
}

0 commit comments

Comments
 (0)