Skip to content

Commit d53c4a0

Browse files
committed
fix: add test cases and split code
1 parent 8e65d4f commit d53c4a0

File tree

4 files changed

+135
-126
lines changed

4 files changed

+135
-126
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
"eslint": "^8.15.0",
5656
"eslint-config-cheminfo-typescript": "^10.4.0",
5757
"jest": "^28.1.0",
58+
"jest-matcher-deep-close-to": "^3.0.2",
5859
"prettier": "^2.6.2",
5960
"rimraf": "^3.0.2",
6061
"ts-jest": "^28.0.1",

src/__tests__/test.ts

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,29 @@
1+
import { toMatchCloseTo } from 'jest-matcher-deep-close-to';
2+
13
import { assign2D } from '..';
24

3-
describe('test myModule', () => {
4-
it('should return 42', () => {
5-
const a = [ 1 , 2 , 3, 4, 5 ];
6-
const b = [1.1, 1.9, 3.1, 3.99, 5.2];
5+
expect.extend({ toMatchCloseTo });
6+
7+
describe('linear sum problem', () => {
8+
it('same number of rows and columns', () => {
9+
const a = [1, 2, 3, 4, 5];
10+
const b = [3.1, 1.1, 1.9, 3.99, 5.2];
711
const diff = a.map((aElement) => {
812
return b.map((bElement) => Math.abs(bElement - aElement));
913
});
10-
console.log('diff', diff)
11-
const result = assign2D(diff, false);
12-
console.log(result)
13-
// expect(myModule()).toBe(42);
14+
const { columnAssignments, rowAssignments } = assign2D(diff, false);
15+
expect(columnAssignments).toMatchCloseTo([2, 0, 1, 3, 4]);
16+
expect(rowAssignments).toMatchCloseTo([1, 2, 0, 3, 4]);
1417
});
18+
19+
it('differents number of rows and columns', () => {
20+
const a = [1, 2, 3, 4, 5, 7];
21+
const b = [3.1, 1.1, 1.9, 3.99, 5.2];
22+
const diff = a.map((aElement) => {
23+
return b.map((bElement) => Math.abs(bElement - aElement));
24+
});
25+
const { columnAssignments, rowAssignments } = assign2D(diff, false);
26+
expect(columnAssignments).toMatchCloseTo([2, 0, 1, 3, 4]);
27+
expect(rowAssignments).toMatchCloseTo([1, 2, 0, 3, 4, -1]);
28+
})
1529
});

src/getShortestPath.ts

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
import { DoubleArray } from 'cheminfo-types';
2+
import sequentialFill from 'ml-array-sequential-fill';
3+
import type { Matrix } from 'ml-matrix';
4+
5+
interface GetShortestPathOptions {
6+
currUnAssCol: number,
7+
dualVariableForColumns: DoubleArray,
8+
dualVariableForRows: DoubleArray,
9+
rowAssignments: DoubleArray,
10+
columnAssignments: DoubleArray,
11+
matrix: Matrix,
12+
}
13+
14+
export function getShortestPath(options: GetShortestPathOptions) {
15+
let {
16+
currUnAssCol,
17+
dualVariableForColumns,
18+
dualVariableForRows,
19+
rowAssignments,
20+
columnAssignments,
21+
matrix,
22+
} = options;
23+
24+
let nbRows = matrix.rows
25+
let nbColumns = matrix.columns;
26+
27+
let pred = new Float64Array(nbColumns);
28+
let scannedColumns = new Float64Array(nbColumns);
29+
let scannedRows = new Float64Array(nbRows);
30+
31+
let rows2Scan = sequentialFill({ from: 0, to: nbRows - 1, size: nbRows });
32+
let numRows2Scan = nbRows;
33+
34+
let sink = -1;
35+
let delta = 0;
36+
let curColumn = currUnAssCol;
37+
let shortestPathCost = getArrayOfInfinity(nbRows)//new Float64Array(nbRows).fill(Number.POSITIVE_INFINITY);
38+
39+
while (sink === -1) {
40+
scannedColumns[curColumn] = 1;
41+
let minVal = Number.POSITIVE_INFINITY;
42+
let closestRowScan = -1;
43+
for (let curRowScan = 0; curRowScan < numRows2Scan; curRowScan++) {
44+
let curRow = rows2Scan[curRowScan];
45+
// console.log(`curRow ${curRow}, ${curRowScan}`)
46+
let reducedCost = delta + matrix.get(curRow, curColumn) - dualVariableForColumns[curColumn] - dualVariableForRows[curRow];
47+
if (reducedCost < shortestPathCost[curRow]) {
48+
pred[curRow] = curColumn;
49+
shortestPathCost[curRow] = reducedCost
50+
}
51+
52+
if (shortestPathCost[curRow] < minVal) {
53+
minVal = shortestPathCost[curRow];
54+
closestRowScan = curRowScan;
55+
}
56+
}
57+
if (!Number.isFinite(minVal)) {
58+
return { dualVariableForColumns, dualVariableForRows, sink, pred };
59+
}
60+
let closestRow = rows2Scan[closestRowScan];
61+
scannedRows[closestRow] = 1;
62+
numRows2Scan -= 1;
63+
rows2Scan.splice(closestRowScan, 1);
64+
delta = shortestPathCost[closestRow];
65+
66+
if (rowAssignments[closestRow] === -1) {
67+
sink = closestRow;
68+
} else {
69+
curColumn = rowAssignments[closestRow];
70+
}
71+
}
72+
dualVariableForColumns[currUnAssCol] += delta;
73+
74+
for (let sel = 0; sel < nbColumns; sel++) {
75+
if (scannedColumns[sel] === 0) continue;
76+
if (sel === currUnAssCol) continue;
77+
dualVariableForColumns[sel] += delta - shortestPathCost[columnAssignments[sel]];
78+
}
79+
for (let sel = 0; sel < nbRows; sel++) {
80+
if (scannedRows[sel] === 0) continue;
81+
dualVariableForRows[sel] -= (delta - shortestPathCost[sel]);
82+
}
83+
84+
return {
85+
sink, pred, dualVariableForColumns, dualVariableForRows
86+
}
87+
}
88+
89+
function getArrayOfInfinity(nbElements = 1, value = Number.POSITIVE_INFINITY) {
90+
const array = new Array(nbElements);
91+
for (let i = 0; i < nbElements; i++) {
92+
array[i] = value;
93+
}
94+
return array;
95+
}

src/index.ts

Lines changed: 17 additions & 118 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import { DoubleArray } from 'cheminfo-types';
2-
import Matrix from 'ml-matrix';
3-
import sequentialFill from 'ml-array-sequential-fill';
2+
import { Matrix } from 'ml-matrix';
3+
4+
import { getShortestPath } from './getShortestPath';
45

5-
let matrix = [[0.1, 1.1, 2.1], [1.1, 0.1, 1.1], [2.1, 1.1, 0.1]]
6-
let assignment = assign2D(matrix, false);
7-
console.log(assignment)
86
export function assign2D(input: DoubleArray[], maximaze = true) {
97
if (input[0].length > input.length) {
108
throw new Error('the matrix should have at least less rows than columns');
@@ -24,8 +22,8 @@ export function assign2D(input: DoubleArray[], maximaze = true) {
2422
let matrixDelta = maximaze ? matrix.max() : matrix.min();
2523
matrix = matrix.subtract(matrixDelta);
2624

27-
let col4row: DoubleArray = new Float64Array(nbRows);
28-
let row4col: DoubleArray = new Float64Array(nbColumns);
25+
let rowAssignments: DoubleArray = new Float64Array(nbRows).fill(-1);
26+
let columnAssignments: DoubleArray = new Float64Array(nbColumns).fill(-1);
2927
let dualVariableForColumns: DoubleArray = new Float64Array(nbColumns);
3028
let dualVariableForRows: DoubleArray = new Float64Array(nbRows);
3129

@@ -35,149 +33,50 @@ export function assign2D(input: DoubleArray[], maximaze = true) {
3533
currUnAssCol,
3634
dualVariableForColumns,
3735
dualVariableForRows,
38-
col4row,
39-
row4col,
36+
rowAssignments,
37+
columnAssignments,
4038
});
4139

4240
let { sink, pred } = currentAugmenting;
43-
console.log('pred', pred)
41+
4442
if (sink === -1) {
4543
return {
46-
col4row: [],
47-
row4col: [],
44+
rowAssignments: [],
45+
columnAssignments: [],
4846
gain: -1,
4947
}
5048
}
5149

5250
dualVariableForColumns = currentAugmenting.dualVariableForColumns;
5351
dualVariableForRows = currentAugmenting.dualVariableForRows;
5452

55-
console.log(dualVariableForColumns, dualVariableForRows)
5653
let j = sink;
57-
console.log(JSON.stringify(sink));
58-
console.log(`j ${j}, currUnAssCol ${currUnAssCol}`)
5954
for (let i = pred[j]; true; i = pred[j]) {
60-
col4row[j] = i;
61-
let h = row4col[i];
62-
row4col[i] = j;
55+
rowAssignments[j] = i;
56+
let h = columnAssignments[i];
57+
columnAssignments[i] = j;
6358
j = h;
6459
if (i === currUnAssCol) break;
6560
}
66-
console.log(JSON.stringify({j, sink}));
6761
}
6862

6963
let gain = 0;
7064
for (let curCol = 0; curCol < nbColumns; curCol++) {
71-
gain += matrix.get(row4col[curCol], curCol);
65+
gain += matrix.get(columnAssignments[curCol], curCol);
7266
}
7367

7468
gain = ((maximaze ? -1 : 1) * gain) + (matrixDelta * nbColumns);
7569

7670
if (didFlip) {
77-
[row4col, col4row] = [col4row, row4col];
71+
[columnAssignments, rowAssignments] = [rowAssignments, columnAssignments];
7872
[dualVariableForColumns, dualVariableForRows] = [dualVariableForRows, dualVariableForColumns];
7973
}
8074

8175
return {
82-
col4row,
83-
row4col,
76+
rowAssignments,
77+
columnAssignments,
8478
gain,
8579
dualVariableForColumns,
8680
dualVariableForRows
8781
}
88-
}
89-
90-
interface GetShortestPathOptions {
91-
currUnAssCol: number,
92-
dualVariableForColumns: DoubleArray,
93-
dualVariableForRows: DoubleArray,
94-
col4row: DoubleArray,
95-
row4col: DoubleArray,
96-
matrix: Matrix,
97-
}
98-
99-
function getShortestPath(options: GetShortestPathOptions) {
100-
let {
101-
currUnAssCol,
102-
dualVariableForColumns,
103-
dualVariableForRows,
104-
col4row,
105-
row4col,
106-
matrix,
107-
} = options;
108-
109-
let nbRows = matrix.rows
110-
let nbColumns = matrix.columns;
111-
112-
let pred = new Float64Array(nbColumns);
113-
let scannedColumns = new Float64Array(nbColumns);
114-
let scannedRows = new Float64Array(nbRows);
115-
116-
let rows2Scan = sequentialFill({from: 0, to: nbRows - 1, size: nbRows});
117-
let numRows2Scan = nbRows;
118-
119-
let sink = -1;
120-
let delta = 0;
121-
let curColumn = currUnAssCol;
122-
let shortestPathCost = getArrayOfInfinity(nbRows);
123-
124-
for (; sink === -1;) {
125-
scannedColumns[curColumn] = 1;
126-
let minVal = Number.POSITIVE_INFINITY;
127-
let closestRowScan = -1;
128-
for (let curRowScan = 0; curRowScan < numRows2Scan; curRowScan++) {
129-
let curRow = rows2Scan[curRowScan];
130-
// console.log(`curRow ${curRow}, ${curRowScan}`)
131-
let reducedCost = delta + matrix.get(curRow, curColumn) - dualVariableForColumns[curColumn] - dualVariableForRows[curRow];
132-
console.log('reduced cost', reducedCost, curColumn, shortestPathCost[curRow])
133-
if (reducedCost < shortestPathCost[curRow]) {
134-
pred[curRow] = curColumn;
135-
shortestPathCost[curRow] = reducedCost
136-
}
137-
138-
if (shortestPathCost[curRow] < minVal) {
139-
minVal = shortestPathCost[curRow];
140-
closestRowScan = curRowScan;
141-
}
142-
}
143-
if (!Number.isFinite(minVal)) {
144-
return { dualVariableForColumns, dualVariableForRows, sink, pred };
145-
}
146-
let closestRow = rows2Scan[closestRowScan];
147-
scannedRows[closestRow] = 1;
148-
numRows2Scan -= 1;
149-
rows2Scan.splice(closestRowScan, 1);
150-
delta = shortestPathCost[closestRow];
151-
152-
if (col4row[closestRow] === 0) {
153-
sink = closestRow;
154-
} else {
155-
curColumn = col4row[closestRow];
156-
}
157-
console.log(`sink ${sink}`);
158-
}
159-
console.log('sale loop sink')
160-
dualVariableForColumns[currUnAssCol] += delta;
161-
162-
for (let sel = 0; sel < nbColumns; sel++) {
163-
if (scannedColumns[sel] === 0) continue;
164-
if (sel === currUnAssCol) continue;
165-
dualVariableForColumns[sel] += delta - shortestPathCost[row4col[sel]];
166-
}
167-
for (let sel = 0; sel < nbRows; sel++) {
168-
if (scannedRows[sel] === 0) continue;
169-
dualVariableForRows[sel] -= (delta - shortestPathCost[sel]);
170-
}
171-
console.log('return')
172-
return {
173-
sink, pred, dualVariableForColumns, dualVariableForRows
174-
}
175-
}
176-
177-
function getArrayOfInfinity(nbElements = 1, value = Number.POSITIVE_INFINITY) {
178-
const array = new Array(nbElements);
179-
for (let i = 0; i < nbElements; i++) {
180-
array[i] = value;
181-
}
182-
return array;
18382
}

0 commit comments

Comments
 (0)