Skip to content

Commit 74b92c2

Browse files
committed
feat: rename the package, add README
1 parent d53c4a0 commit 74b92c2

File tree

5 files changed

+148
-12
lines changed

5 files changed

+148
-12
lines changed

README.md

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,76 @@
1-
# assignRectangular2D
1+
# linear sum assignment
2+
3+
<p align="center">
4+
Package to perform a linear sum assignment even if the cost matrix is rectangular.
5+
</p>
6+
<p align="center">
7+
<img alt="NMReDATA" src="images/linear_assignment.svg">
8+
</p>
9+
10+
11+
This package is the implementation of Jonker-Volgenant shortest
12+
augmenting path algorithm based on the publication [On implementing 2D rectangular assignment algorithms](https://doi.org/10.1109/TAES.2016.140952)
13+
14+
If the number of rows is <= the number of columns, then every row is assigned to one column; otherwise every column is assigned to one row. The assignment minimizes the sum of the assigned elements.
15+
16+
## Usage
17+
18+
```js
19+
import linearSumAssignment from 'linearSumAssignment';
20+
21+
/**
22+
* there is one more value in the experimental values, so one of
23+
* them will be not assigned.
24+
**/
25+
const xValueExperimental = [1, 2, 3, 4, 5, 7];
26+
const xValuePredicted = [3.1, 1.1, 1.9, 3.99, 5.2];
27+
28+
/**
29+
* We will compute a cost matrix where xValueExperimental are
30+
* rows and xValuePredicted in columns.
31+
* In this case we will look for the closest peak for each experimental peak value.
32+
**/
33+
const diff = xValueExperimental.map((experimental) => {
34+
return xValuePredicted.map((predicted) => {
35+
return Math.abs(predicted - experimental);
36+
});
37+
});
38+
39+
const result = linearSumAssignment(diff, { maximaze: false });
40+
console.log(result);
41+
/**
42+
{
43+
rowAssignments: Float64Array(6) [ 1, 2, 0, 3, 4, -1 ],
44+
columnAssignments: Float64Array(5) [ 2, 0, 1, 3, 4 ],
45+
gain: 0.5100000000000002,
46+
dualVariableForColumns: Float64Array(5) [
47+
0.0900000000000003,
48+
0.0900000000000003,
49+
0.0900000000000003,
50+
0,
51+
0.1900000000000004
52+
],
53+
dualVariableForRows: Float64Array(6) [ 0, 0, 0, 0, 0, 0 ]
54+
}
55+
*/
56+
```
57+
58+
`rowAssignments` contains the index of the column assigned to each element in the rows (xValueExperimental). So the first element in xValueExperimental array is assigned to the second element of xValuePredicted.
59+
`columnAssignments` contains the index of the row assigned to
60+
each element in the columns. So the first element in
61+
xValuePredicted is assigned to third element in
62+
xValueExperimental.
63+
`dualVariableForColumns` and `dualVariableForRows` are the Lagrange multipliers or dual variables.
64+
`gain` the sum of the elements in the cost matrix.
65+
## License
66+
67+
[MIT](./LICENSE)
68+
69+
[npm-image]: https://img.shields.io/npm/v/linearSumAssignment.svg
70+
[npm-url]: https://www.npmjs.com/package/linearSumAssignment
71+
[ci-image]: https://github.com/jobo322/linearSumAssignment/workflows/Node.js%20CI/badge.svg?branch=main
72+
[ci-url]: https://github.com/jobo322/linearSumAssignment/actions?query=workflow%3A%22Node.js+CI%22
73+
[codecov-image]: https://img.shields.io/codecov/c/github/jobo322/linearSumAssignment.svg
74+
[codecov-url]: https://codecov.io/gh/jobo322/linearSumAssignment
75+
[download-image]: https://img.shields.io/npm/dm/linearSumAssignment.svg
76+
[download-url]: https://www.npmjs.com/package/linearSumAssignment

images/linear_assignment.svg

Lines changed: 47 additions & 0 deletions
Loading

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
2-
"name": "assignRectangular2D",
2+
"name": "linearSumAssignment",
33
"version": "0.0.0",
4-
"description": "",
4+
"description": "it performs a linear sum assignment even if the cost matrix is rectangular.",
55
"main": "./lib/index.js",
66
"module": "./lib-esm/index.js",
77
"types": "./lib/index.d.ts",

src/__tests__/test.ts

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { toMatchCloseTo } from 'jest-matcher-deep-close-to';
22

3-
import { assign2D } from '..';
3+
import linearSumAssignment from '..';
44

55
expect.extend({ toMatchCloseTo });
66

@@ -11,19 +11,29 @@ describe('linear sum problem', () => {
1111
const diff = a.map((aElement) => {
1212
return b.map((bElement) => Math.abs(bElement - aElement));
1313
});
14-
const { columnAssignments, rowAssignments } = assign2D(diff, false);
14+
const { columnAssignments, rowAssignments } = linearSumAssignment(diff, { maximaze: false});
1515
expect(columnAssignments).toMatchCloseTo([2, 0, 1, 3, 4]);
1616
expect(rowAssignments).toMatchCloseTo([1, 2, 0, 3, 4]);
1717
});
1818

19-
it('differents number of rows and columns', () => {
19+
it('differents size: rows > columns', () => {
2020
const a = [1, 2, 3, 4, 5, 7];
2121
const b = [3.1, 1.1, 1.9, 3.99, 5.2];
2222
const diff = a.map((aElement) => {
2323
return b.map((bElement) => Math.abs(bElement - aElement));
2424
});
25-
const { columnAssignments, rowAssignments } = assign2D(diff, false);
25+
const { columnAssignments, rowAssignments } = linearSumAssignment(diff, { maximaze: false});
2626
expect(columnAssignments).toMatchCloseTo([2, 0, 1, 3, 4]);
2727
expect(rowAssignments).toMatchCloseTo([1, 2, 0, 3, 4, -1]);
28-
})
28+
});
29+
it('differents size: rows < columns', () => {
30+
const a = [3.1, 1.1, 1.9, 3.99, 5.2];
31+
const b = [1, 2, 3, 4, 5, 7];
32+
const diff = a.map((aElement) => {
33+
return b.map((bElement) => Math.abs(bElement - aElement));
34+
});
35+
const { columnAssignments, rowAssignments } = linearSumAssignment(diff, { maximaze: false});
36+
expect(columnAssignments).toMatchCloseTo([1, 2, 0, 3, 4, -1]);
37+
expect(rowAssignments).toMatchCloseTo([2, 0, 1, 3, 4]);
38+
});
2939
});

src/index.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,12 @@ import { Matrix } from 'ml-matrix';
33

44
import { getShortestPath } from './getShortestPath';
55

6-
export function assign2D(input: DoubleArray[], maximaze = true) {
7-
if (input[0].length > input.length) {
8-
throw new Error('the matrix should have at least less rows than columns');
9-
}
6+
interface Options {
7+
maximaze?: boolean
8+
}
9+
10+
export default function linearSumAssignment(input: DoubleArray[], options: Options = {}) {
11+
const { maximaze = true } = options;
1012

1113
let matrix = Matrix.checkMatrix(input);
1214

@@ -21,6 +23,8 @@ export function assign2D(input: DoubleArray[], maximaze = true) {
2123

2224
let matrixDelta = maximaze ? matrix.max() : matrix.min();
2325
matrix = matrix.subtract(matrixDelta);
26+
if (maximaze) matrix = matrix.mul(-1);
27+
2428

2529
let rowAssignments: DoubleArray = new Float64Array(nbRows).fill(-1);
2630
let columnAssignments: DoubleArray = new Float64Array(nbColumns).fill(-1);

0 commit comments

Comments
 (0)