Skip to content

Commit 83bdd83

Browse files
committed
Small fix for end condition + Add base for decimal combination
1 parent 6909d3d commit 83bdd83

3 files changed

Lines changed: 90 additions & 44 deletions

File tree

src/__tests__/decompositions/nnmf.js

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,26 +25,27 @@ function positivity(An) {
2525

2626
describe('Non-negative Matrix Factorization', () => {
2727
it('Factorization test I', () => {
28-
/**
29-
* Global minimum :
30-
* X = [
31-
* [2, 4],
32-
* [8, 16],
33-
* [32, 64],
34-
* [128, 256]]
35-
*
36-
* Y = [
37-
* [1, 2, 3, 4],
38-
* [6, 7, 8, 9]]
39-
*/
28+
/**
29+
* Global minimum :
30+
* X = [
31+
* [2, 4],
32+
* [8, 16],
33+
* [32, 64],
34+
* [128, 256]]
35+
*
36+
* Y = [
37+
* [1, 2, 3, 4],
38+
* [6, 7, 8, 9]]
39+
*/
4040
let A = new Matrix([
4141
[26, 32, 38, 44],
4242
[104, 128, 152, 176],
4343
[416, 512, 608, 704],
4444
[1664, 2048, 2432, 2816]
4545
]);
4646

47-
let nA = new NNMF(A, 1, 0.000001, { version: 2 });
47+
let nA =
48+
new NNMF(A, 1, { targetRelativeError: 0.000001, maxIterations: 10000 });
4849

4950
expect(positivity(nA)).toEqual(true);
5051
expect(nA.error.max()).toBeLessThan(0.000001);
@@ -63,7 +64,7 @@ describe('Non-negative Matrix Factorization', () => {
6364
[1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
6465
]);
6566

66-
let nA = new NNMF(A, 8, 0.1);
67+
let nA = new NNMF(A, 8, { targetRelativeError: 0.5, maxIterations: 10000 });
6768

6869
expect(positivity(nA)).toEqual(true);
6970
expect(nA.error.max()).toBeLessThan(0.5);
@@ -80,7 +81,7 @@ describe('Non-negative Matrix Factorization', () => {
8081
[5, 4, 3, 2, 1, 0, 0, 0, 0, 0]
8182
]);
8283

83-
let nA = new NNMF(A, 3, 0.000000001, 100000);
84+
let nA = new NNMF(A, 3, { targetRelativeError: 1, maxIterations: 10000 });
8485

8586
expect(positivity(nA)).toEqual(true);
8687
expect(nA.error.max()).toBeLessThan(1);

src/dc/nnmf.js

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,19 @@ import { Matrix, WrapperMatrix2D } from '../index';
22

33
/**
44
* @class NNMF
5-
* @link http://papers.nips.cc/paper/1861-algorithms-for-non-negative-matrix-factorization.pdf
5+
* @link
6+
* http://papers.nips.cc/paper/1861-algorithms-for-non-negative-matrix-factorization.pdf
67
* @param {Matrix} A
78
* @param {number} r
8-
* @param {number} targetRelativeError
99
* @param {object} [options={}]
10+
* @param {number} [options.targetRelativeError=0.001]
1011
* @param {number} [options.maxIterations=10000]
1112
* @param {number} [options.version=2]
1213
*/
1314
export default class NNMF {
14-
constructor(A, r, targetRelativeError, options = {}) {
15-
const { maxIterations = 10000, version = 2 } = options;
15+
constructor(A, r, options = {}) {
16+
const { targetRelativeError = 0.001, maxIterations = 10000, version = 2 } =
17+
options;
1618
A = WrapperMatrix2D.checkMatrix(A);
1719
var m = A.rows;
1820
var n = A.columns;
@@ -67,7 +69,8 @@ export default class NNMF {
6769
if (this.A.get(i, j) === 0) {
6870
error.set(i, j, Math.abs(A2.get(i, j) - this.A.get(i, j)));
6971
} else {
70-
error.set(i, j, Math.abs(A2.get(i, j) - this.A.get(i, j)) / this.A.get(i, j));
72+
error.set(i, j, Math.abs(A2.get(i, j) - this.A.get(i, j)) /
73+
this.A.get(i, j));
7174
}
7275
}
7376
}
@@ -112,7 +115,8 @@ function doNnmf1(numberIterations = 1000) {
112115
}
113116
for (let i = 0; i < this.m; i++) {
114117
for (let j = 0; j < this.r; j++) {
115-
this.X.set(i, j, (this.X.get(i, j) * numX.get(i, j)) / denumX.get(i, j));
118+
this.X.set(i, j,
119+
(this.X.get(i, j) * numX.get(i, j)) / denumX.get(i, j));
116120
}
117121
}
118122
A2 = this.X.mmul(this.Y);
@@ -138,7 +142,8 @@ function doNnmf1(numberIterations = 1000) {
138142
}
139143
for (let i = 0; i < this.r; i++) {
140144
for (let j = 0; j < this.n; j++) {
141-
this.Y.set(i, j, (this.Y.get(i, j) * numY.get(i, j)) / denumY.get(i, j));
145+
this.Y.set(i, j,
146+
(this.Y.get(i, j) * numY.get(i, j)) / denumY.get(i, j));
142147
}
143148
}
144149
A2 = this.X.mmul(this.Y);
@@ -167,7 +172,8 @@ function doNnmf2(numberIterations = 1000) {
167172
}
168173
for (let i = 0; i < this.m; i++) {
169174
for (let j = 0; j < this.r; j++) {
170-
this.X.set(i, j, (this.X.get(i, j) * numX.get(i, j)) / denumX.get(i, j));
175+
this.X.set(i, j,
176+
(this.X.get(i, j) * numX.get(i, j)) / denumX.get(i, j));
171177
}
172178
}
173179
numY = this.X.transpose().mmul(this.A);
@@ -180,7 +186,8 @@ function doNnmf2(numberIterations = 1000) {
180186
}
181187
for (let i = 0; i < this.r; i++) {
182188
for (let j = 0; j < this.n; j++) {
183-
this.Y.set(i, j, (this.Y.get(i, j) * numY.get(i, j)) / denumY.get(i, j));
189+
this.Y.set(i, j,
190+
(this.Y.get(i, j) * numY.get(i, j)) / denumY.get(i, j));
184191
}
185192
}
186193
}

src/positiveLinearCombination.js

Lines changed: 58 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { Matrix, WrapperMatrix2D, NNMF } from './index';
22

3-
function linearCombination(X) {
3+
function linearCombination2(X, useDecimal, lowestDecimal) {}
4+
5+
function linearCombination1(X, useDecimal, lowestDecimal) {
46
if (X.rows > 1) {
57
X = X.transpose();
68
}
@@ -14,14 +16,19 @@ function linearCombination(X) {
1416

1517
let notTheEnd = true;
1618
let tmp = 0;
19+
20+
let coef = lowestDecimal;
21+
1722
for (let j = 0; j < maxOcc.columns; j++) {
1823
if (X.get(0, j) !== 0) {
1924
maxOcc.set(0, j, Math.trunc(objValue / X.get(0, j)));
2025
}
2126
}
2227
let testV = 0;
23-
while (notTheEnd && testV < 1000) {
24-
testSolutions.set(0, 0, testSolutions.get(0, 0) + 1);
28+
while (notTheEnd === true) {
29+
if (notTheEnd) {
30+
testSolutions.set(0, 0, testSolutions.get(0, 0) + coef);
31+
}
2532

2633
tmp = 0;
2734

@@ -34,24 +41,24 @@ function linearCombination(X) {
3441
}
3542
diffSolutions = Math.abs(tmp - objValue);
3643
}
37-
38-
if (testSolutions.get(0, testSolutions.columns - 1) > maxOcc.get(0, maxOcc.columns - 1)) {
44+
if (testSolutions.get(0, testSolutions.columns - 1) ===
45+
maxOcc.get(0, maxOcc.columns - 1)) {
3946
notTheEnd = false;
40-
} else {
41-
for (let j = 0; j < maxOcc.columns; j++) {
42-
if (testSolutions.get(0, j) >= maxOcc.get(0, j) && testSolutions.get(0, j) !== 0) {
47+
} else if (notTheEnd) {
48+
for (let j = 0; j < maxOcc.columns - 1; j++) {
49+
if (testSolutions.get(0, j) >= maxOcc.get(0, j) &&
50+
testSolutions.get(0, j) !== 0) {
4351
testSolutions.set(0, j, 0);
44-
testSolutions.set(0, j + 1, testSolutions.get(0, j + 1) + 1);
52+
testSolutions.set(0, j + 1, testSolutions.get(0, j + 1) + coef);
4553
} else if (testSolutions.get(0, j) > maxOcc.get(0, j)) {
4654
testSolutions.set(0, j, 0);
47-
testSolutions.set(0, j + 1, testSolutions.get(0, j + 1) + 1);
55+
testSolutions.set(0, j + 1, testSolutions.get(0, j + 1) + coef);
4856
}
4957
}
5058
}
51-
testV += 1;
5259
}
5360

54-
return (solutions);
61+
return solutions;
5562
}
5663

5764
/**
@@ -61,11 +68,19 @@ function linearCombination(X) {
6168
* @param {object} [options={}]
6269
* @param {number} [options.NNMF_maxIterations=100000]
6370
* @param {number} [options.NNMF_version=2]
71+
* @param {number} [options.PLC_version=1]
6472
* @param {number} [options.delta=1000]
73+
* @param {bool} [options.useDecimal=false]
74+
* @param {bool} [options.lowestDecimal=1]
6575
* @return {Matrix}
6676
*/
6777
export function positiveLinearCombination(base, vector, options = {}) {
68-
const { NNMFmaxIterations = 100000, NNMFversion = 2, delta = 1000 } = options;
78+
const { NNMFmaxIterations = 100000,
79+
NNMFversion = 2,
80+
PLCversion = 1,
81+
delta = 1000,
82+
useDecimal = false,
83+
lowestDecimal = 1 } = options;
6984

7085
base = WrapperMatrix2D.checkMatrix(base);
7186
vector = WrapperMatrix2D.checkMatrix(vector);
@@ -79,10 +94,10 @@ export function positiveLinearCombination(base, vector, options = {}) {
7994
}
8095
if (vector.columns > 1 && vector.rows > 1) {
8196
console.log('ERROR, Vector must be a 1*n or n*1 vector');
82-
return (1);
97+
return 1;
8398
} else if (base.columns !== vector.columns) {
8499
console, log('ERROR, BASE COLUMNS sould be the same as VECTOR COLUMNS');
85-
return (1);
100+
return 1;
86101
} else {
87102
for (let i = 0; i < m - 1; i++) {
88103
for (let j = 0; j < n; j++) {
@@ -93,16 +108,39 @@ export function positiveLinearCombination(base, vector, options = {}) {
93108
A.set(m - 1, j, vector.get(0, j));
94109
}
95110

96-
let nA = new NNMF(A, 1, { maxIterations: NNMFmaxIterations, version: NNMFversion });
97-
111+
let nA = new NNMF(A, 1, {
112+
targetRelativeError: 0.001,
113+
maxIterations: NNMFmaxIterations,
114+
version: NNMFversion
115+
});
98116

99117
for (let i = 0; i < m; i++) {
100-
if ((nA.X.get(m - 1, 0) / delta) > nA.X.get(i, 0)) {
118+
if (nA.X.get(m - 1, 0) / delta > nA.X.get(i, 0)) {
101119
nA.X.set(i, 0, 0);
102120
}
103121
}
122+
if (PLCversion === 1) {
123+
solutions = linearCombination1(nA.X, useDecimal, lowestDecimal);
124+
} else if (PLCversion === 2) {
125+
solutions = linearCombination2(nA.X, useDecimal, lowestDecimal);
126+
}
104127

105-
solutions = linearCombination(nA.X, nA.X.min() + Number.EPSILON);
106-
return (solutions);
128+
return solutions;
107129
}
108130
}
131+
132+
let base = new Matrix([
133+
[0, 20, 100, 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
134+
[0, 0, 0, 0, 0, 30, 100, 30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
135+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 5, 100, 5, 0, 0, 0, 0, 0, 0],
136+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 15, 100, 15, 0, 0, 0],
137+
[0, 0, 0, 0, 0, 0, 0, 10, 100, 10, 0, 0, 0, 0, 0, 0, 0, 0],
138+
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 10, 100, 10]
139+
]);
140+
let vector = new Matrix(
141+
[[0, 20, 100, 20, 0, 0, 0, 0, 0, 5, 100, 5, 0, 0, 0, 20, 200, 20]]);
142+
let solutions = Matrix.zeros(1, base.columns);
143+
144+
solutions = positiveLinearCombination(base, vector);
145+
146+
console.table(solutions);

0 commit comments

Comments
 (0)