w-cluster 1.0.20 → 1.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/workflows/ci-test.yml +3 -3
- package/README.md +4 -6
- package/babel.config.js +4 -3
- package/dist/w-cluster.umd.js +2 -2
- package/dist/w-cluster.umd.js.map +1 -1
- package/dist/w-cluster.wk.umd.js +1 -1
- package/docs/DenseMatrix.html +202 -0
- package/docs/DistancePair.html +202 -0
- package/docs/LowerTriangle.html +202 -0
- package/docs/Rec.html +202 -0
- package/docs/Reco.html +202 -0
- package/docs/WCluster.mjs.html +8 -4
- package/docs/examples/ex-PCA.html +1 -1
- package/docs/examples/ex-cluster-webworker.html +1 -1
- package/docs/examples/ex-cluster.html +1 -1
- package/docs/global.html +6799 -164
- package/docs/index.html +2 -2
- package/docs/k-medoids_alternating.mjs.html +144 -0
- package/docs/k-medoids_arrayadapter.mjs.html +107 -0
- package/docs/k-medoids_dynmsc.mjs.html +255 -0
- package/docs/k-medoids_fastermsc.mjs.html +411 -0
- package/docs/k-medoids_fasterpam.mjs.html +305 -0
- package/docs/k-medoids_fastmsc.mjs.html +166 -0
- package/docs/k-medoids_fastpam1.mjs.html +132 -0
- package/docs/k-medoids_initialization.mjs.html +107 -0
- package/docs/k-medoids_pam.mjs.html +275 -0
- package/docs/k-medoids_pammedsil.mjs.html +307 -0
- package/docs/k-medoids_pamsil.mjs.html +225 -0
- package/docs/k-medoids_par_fasterpam.mjs.html +134 -0
- package/docs/k-medoids_par_silhouette.mjs.html +126 -0
- package/docs/k-medoids_silhouette.mjs.html +193 -0
- package/docs/k-medoids_util.mjs.html +135 -0
- package/g-PCA-nodeworker.mjs +1 -1
- package/g-PCA.mjs +1 -1
- package/g-cluster-kMeans-large.mjs +83 -0
- package/g-cluster-kMeans-nodeworker.mjs +181 -0
- package/g-cluster-kMeans.mjs +178 -0
- package/g-cluster-kMedoids-large-suggest.mjs +95 -0
- package/g-cluster-kMedoids-large.mjs +104 -0
- package/{g-cluster-nodeworker.mjs → g-cluster-kMedoids-nodeworker.mjs} +1 -2
- package/{g-cluster.mjs → g-cluster-kMedoids-simple.mjs} +1 -1
- package/package.json +7 -7
- package/script.txt +5 -7
- package/src/WCluster.mjs +6 -2
- package/src/WClusterCore.mjs +30 -21
- package/src/WClusterMat.mjs +98 -24
- package/src/jaccardBitset.mjs +63 -0
- package/src/k-medoids/README.md +124 -0
- package/src/k-medoids/alternating.mjs +72 -0
- package/src/k-medoids/arrayadapter.mjs +35 -0
- package/src/k-medoids/dynmsc.mjs +183 -0
- package/src/k-medoids/fastermsc.mjs +339 -0
- package/src/k-medoids/fasterpam.mjs +233 -0
- package/src/k-medoids/fastmsc.mjs +94 -0
- package/src/k-medoids/fastpam1.mjs +60 -0
- package/src/k-medoids/index.mjs +37 -0
- package/src/k-medoids/initialization.mjs +35 -0
- package/src/k-medoids/package.json +8 -0
- package/src/k-medoids/pam.mjs +203 -0
- package/src/k-medoids/pammedsil.mjs +235 -0
- package/src/k-medoids/pamsil.mjs +153 -0
- package/src/k-medoids/par_fasterpam.mjs +62 -0
- package/src/k-medoids/par_silhouette.mjs +54 -0
- package/src/k-medoids/silhouette.mjs +121 -0
- package/src/k-medoids/test.mjs +357 -0
- package/src/k-medoids/util.mjs +63 -0
- package/test/jaccardBitset.test.mjs +141 -0
- package/test/kMeans.test.mjs +278 -0
- package/test/kMedoids-large-suggest.test.mjs +140 -0
- package/test/kMedoids-large.test.mjs +142 -0
- package/test/{cluster.test.mjs → kMedoids-simple.test.mjs} +5 -5
- package/toolg/gDocsExams.mjs +1 -1
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<title>k-medoids/pammedsil.mjs - Documentation</title>
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
<script src="scripts/prettify/prettify.js"></script>
|
|
10
|
+
<script src="scripts/prettify/lang-css.js"></script>
|
|
11
|
+
<!--[if lt IE 9]>
|
|
12
|
+
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
|
13
|
+
<![endif]-->
|
|
14
|
+
<link type="text/css" rel="stylesheet" href="styles/prettify.css">
|
|
15
|
+
<link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
|
|
16
|
+
<script src="scripts/nav.js" defer></script>
|
|
17
|
+
|
|
18
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
19
|
+
</head>
|
|
20
|
+
<body>
|
|
21
|
+
|
|
22
|
+
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
|
|
23
|
+
<label for="nav-trigger" class="navicon-button x">
|
|
24
|
+
<div class="navicon"></div>
|
|
25
|
+
</label>
|
|
26
|
+
|
|
27
|
+
<label for="nav-trigger" class="overlay"></label>
|
|
28
|
+
|
|
29
|
+
<nav >
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="DenseMatrix.html">DenseMatrix</a></li><li><a href="DistancePair.html">DistancePair</a></li><li><a href="LowerTriangle.html">LowerTriangle</a></li><li><a href="Rec.html">Rec</a></li><li><a href="Reco.html">Reco</a></li></ul><h3>Global</h3><ul><li><a href="global.html#PCA">PCA</a></li><li><a href="global.html#WCluster">WCluster</a></li><li><a href="global.html#alternating">alternating</a></li><li><a href="global.html#arrayAdapter">arrayAdapter</a></li><li><a href="global.html#assign_nearest">assign_nearest</a></li><li><a href="global.html#choose_medoid_within_partition">choose_medoid_within_partition</a></li><li><a href="global.html#cluster">cluster</a></li><li><a href="global.html#do_swap">do_swap</a></li><li><a href="global.html#do_swap_k2">do_swap_k2</a></li><li><a href="global.html#dynmsc">dynmsc</a></li><li><a href="global.html#fastermsc">fastermsc</a></li><li><a href="global.html#fastermsc_k2">fastermsc_k2</a></li><li><a href="global.html#fasterpam">fasterpam</a></li><li><a href="global.html#fastmsc">fastmsc</a></li><li><a href="global.html#fastmsc_k2">fastmsc_k2</a></li><li><a href="global.html#fastpam1">fastpam1</a></li><li><a href="global.html#find_best_swap">find_best_swap</a></li><li><a href="global.html#find_best_swap_k2">find_best_swap_k2</a></li><li><a href="global.html#find_best_swap_pam">find_best_swap_pam</a></li><li><a href="global.html#find_best_swap_pammedsil">find_best_swap_pammedsil</a></li><li><a href="global.html#find_best_swap_pammedsil_k2">find_best_swap_pammedsil_k2</a></li><li><a href="global.html#find_max">find_max</a></li><li><a href="global.html#find_min">find_min</a></li><li><a href="global.html#first_k">first_k</a></li><li><a href="global.html#initial_assignment">initial_assignment</a></li><li><a href="global.html#initial_assignment_k2">initial_assignment_k2</a></li><li><a href="global.html#medoid_silhouette">medoid_silhouette</a></li><li><a href="global.html#pam">pam</a></li><li><a href="global.html#pam_build">pam_build</a></li><li><a href="global.html#pam_build_initialize">pam_build_initialize</a></li><li><a href="global.html#pam_optimize">pam_optimize</a></li><li><a href="global.html#pam_swap">pam_swap</a></li><li><a href="global.html#pammedsil">pammedsil</a></li><li><a href="global.html#pammedsil_build_initialize">pammedsil_build_initialize</a></li><li><a href="global.html#pammedsil_optimize">pammedsil_optimize</a></li><li><a href="global.html#pammedsil_swap">pammedsil_swap</a></li><li><a href="global.html#pamsil">pamsil</a></li><li><a href="global.html#pamsil_build_initialize">pamsil_build_initialize</a></li><li><a href="global.html#pamsil_optimize">pamsil_optimize</a></li><li><a href="global.html#pamsil_swap">pamsil_swap</a></li><li><a href="global.html#par_fasterpam">par_fasterpam</a></li><li><a href="global.html#par_silhouette">par_silhouette</a></li><li><a href="global.html#rand_fasterpam">rand_fasterpam</a></li><li><a href="global.html#random_initialization">random_initialization</a></li><li><a href="global.html#remove_med">remove_med</a></li><li><a href="global.html#sample">sample</a></li><li><a href="global.html#shuffle">shuffle</a></li><li><a href="global.html#silhouette">silhouette</a></li><li><a href="global.html#update_removal_loss">update_removal_loss</a></li><li><a href="global.html#update_second_nearest">update_second_nearest</a></li><li><a href="global.html#update_third_nearest">update_third_nearest</a></li><li><a href="global.html#update_third_nearest_without_new">update_third_nearest_without_new</a></li></ul>
|
|
33
|
+
|
|
34
|
+
</nav>
|
|
35
|
+
|
|
36
|
+
<div id="main">
|
|
37
|
+
|
|
38
|
+
<h1 class="page-title">k-medoids/pammedsil.mjs</h1>
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
<section>
|
|
47
|
+
<article>
|
|
48
|
+
<pre class="prettyprint source linenums"><code>// Ported 1:1 from src/pammedsil.rs (PAMMEDSIL algorithm).
|
|
49
|
+
import { arrayAdapter } from './arrayadapter.mjs';
|
|
50
|
+
import { Reco, DistancePair, U32_MAX, USIZE_MAX, choose_medoid_within_partition } from './util.mjs';
|
|
51
|
+
import { _loss, initial_assignment, do_swap } from './fastermsc.mjs';
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Run the original PAMMEDSIL SWAP algorithm (no initialization, but given initial medoids).
|
|
55
|
+
* @param {object} mat - pairwise distance matrix
|
|
56
|
+
* @param {number[]} med - the list of medoids (mutated in place)
|
|
57
|
+
* @param {number} maxiter - the maximum number of iterations allowed
|
|
58
|
+
* returns { loss, assi, nIter, nSwaps }
|
|
59
|
+
*/
|
|
60
|
+
export function pammedsil_swap(mat, med, maxiter) {
|
|
61
|
+
mat = arrayAdapter(mat);
|
|
62
|
+
const [loss, data] = initial_assignment(mat, med);
|
|
63
|
+
return pammedsil_optimize(mat, med, data, maxiter, loss);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Run the original PAM BUILD algorithm combined with the PAMMEDSIL SWAP.
|
|
68
|
+
* @param {object} mat - pairwise distance matrix
|
|
69
|
+
* @param {number} k - the number of medoids to pick
|
|
70
|
+
* @param {number} maxiter - the maximum number of iterations allowed
|
|
71
|
+
* returns { loss, assi, meds, nIter, nSwaps }
|
|
72
|
+
*/
|
|
73
|
+
export function pammedsil(mat, k, maxiter) {
|
|
74
|
+
mat = arrayAdapter(mat);
|
|
75
|
+
const n = mat.len();
|
|
76
|
+
if (!mat.isSquare()) throw new Error('Dissimilarity matrix is not square');
|
|
77
|
+
if (!(n <= U32_MAX)) throw new Error('N is too large');
|
|
78
|
+
if (!(k > 0 && k < U32_MAX)) throw new Error('invalid N');
|
|
79
|
+
if (!(k <= n)) throw new Error('k must be at most N');
|
|
80
|
+
const meds = [];
|
|
81
|
+
const data = [];
|
|
82
|
+
const loss = pammedsil_build_initialize(mat, meds, data, k);
|
|
83
|
+
const { loss: nloss, assi, nIter, nSwaps } = pammedsil_optimize(mat, meds, data, maxiter, loss);
|
|
84
|
+
return { loss: nloss, assi, meds, nIter, nSwaps }; // also return medoids
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Main optimization function of PAMMEDSIL, not exposed (use pammedsil_swap or pammedsil) */
|
|
88
|
+
function pammedsil_optimize(mat, med, data, maxiter, loss) {
|
|
89
|
+
const n = mat.len(), k = med.length;
|
|
90
|
+
if (k === 1) {
|
|
91
|
+
const assi = new Array(n).fill(0);
|
|
92
|
+
const [swapped, lloss] = choose_medoid_within_partition(mat, assi, med, 0);
|
|
93
|
+
return { loss: lloss, assi, nIter: 1, nSwaps: swapped ? 1 : 0 };
|
|
94
|
+
}
|
|
95
|
+
let n_swaps = 0, iter = 0;
|
|
96
|
+
while (iter < maxiter) {
|
|
97
|
+
iter += 1;
|
|
98
|
+
let best = [0, k, USIZE_MAX];
|
|
99
|
+
for (let j = 0; j < n; j++) {
|
|
100
|
+
if (j === med[data[j].near.i]) {
|
|
101
|
+
continue; // This already is a medoid
|
|
102
|
+
}
|
|
103
|
+
const [change, b] = (k === 2)
|
|
104
|
+
? find_best_swap_pammedsil_k2(mat, med, data, j)
|
|
105
|
+
: find_best_swap_pammedsil(mat, med, data, j);
|
|
106
|
+
if (change <= best[0]) {
|
|
107
|
+
continue; // No improvement
|
|
108
|
+
}
|
|
109
|
+
best = [change, b, j];
|
|
110
|
+
}
|
|
111
|
+
if (best[0] > 0) {
|
|
112
|
+
n_swaps += 1;
|
|
113
|
+
// perform the swap
|
|
114
|
+
const newloss = do_swap(mat, med, data, best[1], best[2]);
|
|
115
|
+
if (newloss >= loss) {
|
|
116
|
+
break; // Probably numerically unstable now.
|
|
117
|
+
}
|
|
118
|
+
loss = newloss;
|
|
119
|
+
} else {
|
|
120
|
+
break; // No improvement, or NaN.
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const assi = data.map((x) => x.near.i);
|
|
124
|
+
loss = 1 - loss / n;
|
|
125
|
+
return { loss, assi, nIter: iter, nSwaps: n_swaps };
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** Find the best swap for object j. Returns [change, b]. */
|
|
129
|
+
function find_best_swap_pammedsil(mat, med, data, j) {
|
|
130
|
+
const recj = data[j];
|
|
131
|
+
let best = [0, USIZE_MAX];
|
|
132
|
+
for (let m = 0; m < med.length; m++) {
|
|
133
|
+
let acc = _loss(recj.near.d, recj.seco.d); // j becomes medoid
|
|
134
|
+
for (let o = 0; o < data.length; o++) {
|
|
135
|
+
const reco = data[o];
|
|
136
|
+
if (o === j) {
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
const doj = mat.get(o, j);
|
|
140
|
+
// Current medoid is being replaced:
|
|
141
|
+
if (reco.near.i === m) {
|
|
142
|
+
if (doj < reco.seco.d) {
|
|
143
|
+
// Assign to new medoid:
|
|
144
|
+
acc += _loss(reco.near.d, reco.seco.d) - _loss(doj, reco.seco.d);
|
|
145
|
+
} else if (doj < reco.third.d) {
|
|
146
|
+
// Assign to second nearest instead:
|
|
147
|
+
acc += _loss(reco.near.d, reco.seco.d) - _loss(reco.seco.d, doj);
|
|
148
|
+
} else {
|
|
149
|
+
acc += _loss(reco.near.d, reco.seco.d) - _loss(reco.seco.d, reco.third.d);
|
|
150
|
+
}
|
|
151
|
+
} else if (reco.seco.i === m) {
|
|
152
|
+
if (doj < reco.near.d) {
|
|
153
|
+
acc += _loss(reco.near.d, reco.seco.d) - _loss(doj, reco.near.d);
|
|
154
|
+
} else if (doj < reco.third.d) {
|
|
155
|
+
acc += _loss(reco.near.d, reco.seco.d) - _loss(reco.near.d, doj);
|
|
156
|
+
} else {
|
|
157
|
+
acc += _loss(reco.near.d, reco.seco.d) - _loss(reco.near.d, reco.third.d);
|
|
158
|
+
}
|
|
159
|
+
} else {
|
|
160
|
+
if (doj < reco.near.d) {
|
|
161
|
+
acc += _loss(reco.near.d, reco.seco.d) - _loss(doj, reco.near.d);
|
|
162
|
+
} else if (doj < reco.seco.d) {
|
|
163
|
+
acc += _loss(reco.near.d, reco.seco.d) - _loss(reco.near.d, doj);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (acc > best[0]) {
|
|
168
|
+
best = [acc, m];
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return best;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/** Find the best swap for object j (k=2 variant). Returns [change, b]. */
|
|
175
|
+
function find_best_swap_pammedsil_k2(mat, med, data, j) {
|
|
176
|
+
const recj = data[j];
|
|
177
|
+
let best = [0, USIZE_MAX];
|
|
178
|
+
for (let m = 0; m < med.length; m++) {
|
|
179
|
+
let acc = _loss(recj.near.d, recj.seco.d); // j becomes medoid
|
|
180
|
+
for (let o = 0; o < data.length; o++) {
|
|
181
|
+
const reco = data[o];
|
|
182
|
+
if (o === j) {
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
const doj = mat.get(o, j);
|
|
186
|
+
// Current medoid is being replaced:
|
|
187
|
+
if (reco.near.i === m) {
|
|
188
|
+
if (doj < reco.seco.d) {
|
|
189
|
+
// Assign to new medoid:
|
|
190
|
+
acc += _loss(reco.near.d, reco.seco.d) - _loss(doj, reco.seco.d);
|
|
191
|
+
} else {
|
|
192
|
+
// Assign to second nearest instead:
|
|
193
|
+
acc += _loss(reco.near.d, reco.seco.d) - _loss(reco.seco.d, doj);
|
|
194
|
+
}
|
|
195
|
+
} else if (reco.seco.i === m) {
|
|
196
|
+
if (doj < reco.near.d) {
|
|
197
|
+
acc += _loss(reco.near.d, reco.seco.d) - _loss(doj, reco.near.d);
|
|
198
|
+
} else {
|
|
199
|
+
acc += _loss(reco.near.d, reco.seco.d) - _loss(reco.near.d, doj);
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
if (doj < reco.near.d) {
|
|
203
|
+
acc += _loss(reco.near.d, reco.seco.d) - _loss(doj, reco.near.d);
|
|
204
|
+
} else if (doj < reco.seco.d) {
|
|
205
|
+
acc += _loss(reco.near.d, reco.seco.d) - _loss(reco.near.d, doj);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
if (acc > best[0]) {
|
|
210
|
+
best = [acc, m];
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return best;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/** Not exposed. Use pammedsil_build or pammedsil. Pushes into meds & data. Returns loss. */
|
|
217
|
+
function pammedsil_build_initialize(mat, meds, data, k) {
|
|
218
|
+
const n = mat.len();
|
|
219
|
+
// choose first medoid
|
|
220
|
+
let best = [0, k];
|
|
221
|
+
for (let i = 0; i < n; i++) {
|
|
222
|
+
let sum = 0;
|
|
223
|
+
for (let j = 0; j < n; j++) {
|
|
224
|
+
if (j !== i) {
|
|
225
|
+
sum += mat.get(j, i);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (i === 0 || sum < best[0]) {
|
|
229
|
+
best = [sum, i];
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
let loss = best[0];
|
|
233
|
+
meds.push(best[1]);
|
|
234
|
+
for (let j = 0; j < n; j++) {
|
|
235
|
+
data.push(new Reco(0, mat.get(j, best[1]), U32_MAX, 0, U32_MAX, 0));
|
|
236
|
+
}
|
|
237
|
+
// choose remaining medoids
|
|
238
|
+
for (let l = 1; l < k; l++) {
|
|
239
|
+
best = [0, k];
|
|
240
|
+
for (let i = 1; i < data.length; i++) {
|
|
241
|
+
let sum = -data[i].near.d;
|
|
242
|
+
for (let jj = 0; jj < data.length; jj++) {
|
|
243
|
+
const dj = data[jj];
|
|
244
|
+
if (jj !== i) {
|
|
245
|
+
const d = mat.get(jj, i);
|
|
246
|
+
if (d < dj.near.d) {
|
|
247
|
+
sum += d - dj.near.d;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
if (i === 0 || sum < best[0]) {
|
|
252
|
+
best = [sum, i];
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
if (best[0] >= 0) { break; } // No more improvement, duplicates
|
|
256
|
+
// Update assignments:
|
|
257
|
+
loss = 0;
|
|
258
|
+
for (let jj = 0; jj < data.length; jj++) {
|
|
259
|
+
const recj = data[jj];
|
|
260
|
+
if (jj === best[1]) {
|
|
261
|
+
recj.third = recj.seco.clone();
|
|
262
|
+
recj.seco = recj.near.clone();
|
|
263
|
+
recj.near = new DistancePair(l, 0);
|
|
264
|
+
} else {
|
|
265
|
+
const dj = mat.get(jj, best[1]);
|
|
266
|
+
if (dj < recj.near.d) {
|
|
267
|
+
recj.third = recj.seco.clone();
|
|
268
|
+
recj.seco = recj.near.clone();
|
|
269
|
+
recj.near = new DistancePair(l, dj);
|
|
270
|
+
} else if (recj.seco.i === U32_MAX || dj < recj.seco.d) {
|
|
271
|
+
recj.third = recj.seco.clone();
|
|
272
|
+
recj.seco = new DistancePair(l, dj);
|
|
273
|
+
} else if (recj.third.i === U32_MAX || dj < recj.third.d) {
|
|
274
|
+
recj.third = new DistancePair(l, dj);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
loss += _loss(recj.near.d, recj.seco.d);
|
|
278
|
+
}
|
|
279
|
+
meds.push(best[1]);
|
|
280
|
+
}
|
|
281
|
+
return loss;
|
|
282
|
+
}
|
|
283
|
+
</code></pre>
|
|
284
|
+
</article>
|
|
285
|
+
</section>
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
</div>
|
|
293
|
+
|
|
294
|
+
<br class="clear">
|
|
295
|
+
|
|
296
|
+
<footer>
|
|
297
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Jun 14 2026 10:59:21 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
298
|
+
</footer>
|
|
299
|
+
|
|
300
|
+
<script>prettyPrint();</script>
|
|
301
|
+
<script src="scripts/polyfill.js"></script>
|
|
302
|
+
<script src="scripts/linenumber.js"></script>
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
</body>
|
|
307
|
+
</html>
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<title>k-medoids/pamsil.mjs - Documentation</title>
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
<script src="scripts/prettify/prettify.js"></script>
|
|
10
|
+
<script src="scripts/prettify/lang-css.js"></script>
|
|
11
|
+
<!--[if lt IE 9]>
|
|
12
|
+
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
|
|
13
|
+
<![endif]-->
|
|
14
|
+
<link type="text/css" rel="stylesheet" href="styles/prettify.css">
|
|
15
|
+
<link type="text/css" rel="stylesheet" href="styles/jsdoc.css">
|
|
16
|
+
<script src="scripts/nav.js" defer></script>
|
|
17
|
+
|
|
18
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
19
|
+
</head>
|
|
20
|
+
<body>
|
|
21
|
+
|
|
22
|
+
<input type="checkbox" id="nav-trigger" class="nav-trigger" />
|
|
23
|
+
<label for="nav-trigger" class="navicon-button x">
|
|
24
|
+
<div class="navicon"></div>
|
|
25
|
+
</label>
|
|
26
|
+
|
|
27
|
+
<label for="nav-trigger" class="overlay"></label>
|
|
28
|
+
|
|
29
|
+
<nav >
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
<h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="DenseMatrix.html">DenseMatrix</a></li><li><a href="DistancePair.html">DistancePair</a></li><li><a href="LowerTriangle.html">LowerTriangle</a></li><li><a href="Rec.html">Rec</a></li><li><a href="Reco.html">Reco</a></li></ul><h3>Global</h3><ul><li><a href="global.html#PCA">PCA</a></li><li><a href="global.html#WCluster">WCluster</a></li><li><a href="global.html#alternating">alternating</a></li><li><a href="global.html#arrayAdapter">arrayAdapter</a></li><li><a href="global.html#assign_nearest">assign_nearest</a></li><li><a href="global.html#choose_medoid_within_partition">choose_medoid_within_partition</a></li><li><a href="global.html#cluster">cluster</a></li><li><a href="global.html#do_swap">do_swap</a></li><li><a href="global.html#do_swap_k2">do_swap_k2</a></li><li><a href="global.html#dynmsc">dynmsc</a></li><li><a href="global.html#fastermsc">fastermsc</a></li><li><a href="global.html#fastermsc_k2">fastermsc_k2</a></li><li><a href="global.html#fasterpam">fasterpam</a></li><li><a href="global.html#fastmsc">fastmsc</a></li><li><a href="global.html#fastmsc_k2">fastmsc_k2</a></li><li><a href="global.html#fastpam1">fastpam1</a></li><li><a href="global.html#find_best_swap">find_best_swap</a></li><li><a href="global.html#find_best_swap_k2">find_best_swap_k2</a></li><li><a href="global.html#find_best_swap_pam">find_best_swap_pam</a></li><li><a href="global.html#find_best_swap_pammedsil">find_best_swap_pammedsil</a></li><li><a href="global.html#find_best_swap_pammedsil_k2">find_best_swap_pammedsil_k2</a></li><li><a href="global.html#find_max">find_max</a></li><li><a href="global.html#find_min">find_min</a></li><li><a href="global.html#first_k">first_k</a></li><li><a href="global.html#initial_assignment">initial_assignment</a></li><li><a href="global.html#initial_assignment_k2">initial_assignment_k2</a></li><li><a href="global.html#medoid_silhouette">medoid_silhouette</a></li><li><a href="global.html#pam">pam</a></li><li><a href="global.html#pam_build">pam_build</a></li><li><a href="global.html#pam_build_initialize">pam_build_initialize</a></li><li><a href="global.html#pam_optimize">pam_optimize</a></li><li><a href="global.html#pam_swap">pam_swap</a></li><li><a href="global.html#pammedsil">pammedsil</a></li><li><a href="global.html#pammedsil_build_initialize">pammedsil_build_initialize</a></li><li><a href="global.html#pammedsil_optimize">pammedsil_optimize</a></li><li><a href="global.html#pammedsil_swap">pammedsil_swap</a></li><li><a href="global.html#pamsil">pamsil</a></li><li><a href="global.html#pamsil_build_initialize">pamsil_build_initialize</a></li><li><a href="global.html#pamsil_optimize">pamsil_optimize</a></li><li><a href="global.html#pamsil_swap">pamsil_swap</a></li><li><a href="global.html#par_fasterpam">par_fasterpam</a></li><li><a href="global.html#par_silhouette">par_silhouette</a></li><li><a href="global.html#rand_fasterpam">rand_fasterpam</a></li><li><a href="global.html#random_initialization">random_initialization</a></li><li><a href="global.html#remove_med">remove_med</a></li><li><a href="global.html#sample">sample</a></li><li><a href="global.html#shuffle">shuffle</a></li><li><a href="global.html#silhouette">silhouette</a></li><li><a href="global.html#update_removal_loss">update_removal_loss</a></li><li><a href="global.html#update_second_nearest">update_second_nearest</a></li><li><a href="global.html#update_third_nearest">update_third_nearest</a></li><li><a href="global.html#update_third_nearest_without_new">update_third_nearest_without_new</a></li></ul>
|
|
33
|
+
|
|
34
|
+
</nav>
|
|
35
|
+
|
|
36
|
+
<div id="main">
|
|
37
|
+
|
|
38
|
+
<h1 class="page-title">k-medoids/pamsil.mjs</h1>
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
<section>
|
|
47
|
+
<article>
|
|
48
|
+
<pre class="prettyprint source linenums"><code>// PAMSIL: PAM combined with direct Silhouette optimization.
|
|
49
|
+
// Ported 1:1 from src/pamsil.rs
|
|
50
|
+
|
|
51
|
+
import { arrayAdapter } from './arrayadapter.mjs';
|
|
52
|
+
import { USIZE_MAX, choose_medoid_within_partition } from './util.mjs';
|
|
53
|
+
import { assign_nearest } from './alternating.mjs';
|
|
54
|
+
import { silhouette } from './silhouette.mjs';
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Run the original PAMSIL SWAP algorithm (no BUILD, but given initial medoids).
|
|
58
|
+
*
|
|
59
|
+
* @param {*} mat - a pairwise distance matrix
|
|
60
|
+
* @param {number[]} med - the list of medoids (mutated in place)
|
|
61
|
+
* @param {number} maxiter - the maximum number of iterations allowed
|
|
62
|
+
* @returns {{ loss: number, assi: number[], nIter: number, nSwaps: number }}
|
|
63
|
+
*/
|
|
64
|
+
export function pamsil(mat, k, maxiter) {
|
|
65
|
+
mat = arrayAdapter(mat);
|
|
66
|
+
const n = mat.len();
|
|
67
|
+
if (!mat.isSquare()) throw new Error('Dissimilarity matrix is not square');
|
|
68
|
+
if (!(n <= 4294967295)) throw new Error('N is too large');
|
|
69
|
+
if (!(k > 0 && k < 4294967295)) throw new Error('invalid N');
|
|
70
|
+
if (!(k <= n)) throw new Error('k must be at most N');
|
|
71
|
+
const meds = [];
|
|
72
|
+
const assi = new Array(n).fill(0);
|
|
73
|
+
pamsil_build_initialize(mat, meds, assi, k);
|
|
74
|
+
const [nloss, n_iter, n_swap] = pamsil_optimize(mat, meds, assi, maxiter);
|
|
75
|
+
return { loss: nloss, assi, meds, nIter: n_iter, nSwaps: n_swap }; // also return medoids
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Run the original PAMSIL SWAP algorithm (no BUILD, but given initial medoids).
|
|
80
|
+
*
|
|
81
|
+
* @param {*} mat - a pairwise distance matrix
|
|
82
|
+
* @param {number[]} med - the list of medoids (mutated in place)
|
|
83
|
+
* @param {number} maxiter - the maximum number of iterations allowed
|
|
84
|
+
* @returns {{ loss: number, assi: number[], nIter: number, nSwaps: number }}
|
|
85
|
+
*/
|
|
86
|
+
export function pamsil_swap(mat, med, maxiter) {
|
|
87
|
+
mat = arrayAdapter(mat);
|
|
88
|
+
const n = mat.len();
|
|
89
|
+
const assi = new Array(n).fill(0);
|
|
90
|
+
assign_nearest(mat, med, assi);
|
|
91
|
+
const [nloss, n_iter, n_swap] = pamsil_optimize(mat, med, assi, maxiter);
|
|
92
|
+
return { loss: nloss, assi, nIter: n_iter, nSwaps: n_swap };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Main optimization function of PAMSIL, not exposed (use pamsil_swap or pamsil).
|
|
97
|
+
* @returns {[number, number, number]} [loss, n_iter, n_swaps]
|
|
98
|
+
*/
|
|
99
|
+
function pamsil_optimize(mat, med, assi, maxiter) {
|
|
100
|
+
const n = mat.len(), k = med.length;
|
|
101
|
+
if (k === 1) {
|
|
102
|
+
const [swapped, loss] = choose_medoid_within_partition(mat, assi, med, 0);
|
|
103
|
+
return [loss, 1, swapped ? 1 : 0];
|
|
104
|
+
}
|
|
105
|
+
let n_swaps = 0, iter = 0;
|
|
106
|
+
let sil = silhouette(mat, assi, false).sil;
|
|
107
|
+
while (iter < maxiter) {
|
|
108
|
+
iter += 1;
|
|
109
|
+
let best = [0, k, USIZE_MAX];
|
|
110
|
+
for (let m = 0; m < k; m++) {
|
|
111
|
+
const medm = med[m]; // preseve previous value
|
|
112
|
+
for (let j = 0; j < n; j++) {
|
|
113
|
+
if (j === medm || j === med[assi[j]]) {
|
|
114
|
+
continue; // This already is a medoid
|
|
115
|
+
}
|
|
116
|
+
med[m] = j; // replace
|
|
117
|
+
assign_nearest(mat, med, assi);
|
|
118
|
+
const siltemp = silhouette(mat, assi, false).sil;
|
|
119
|
+
if (siltemp <= best[0]) {
|
|
120
|
+
continue; // No improvement
|
|
121
|
+
}
|
|
122
|
+
best = [siltemp, m, j];
|
|
123
|
+
}
|
|
124
|
+
med[m] = medm; // restore
|
|
125
|
+
}
|
|
126
|
+
if (best[0] <= sil) {
|
|
127
|
+
break; // no improvement
|
|
128
|
+
}
|
|
129
|
+
n_swaps += 1;
|
|
130
|
+
med[best[1]] = best[2];
|
|
131
|
+
sil = best[0];
|
|
132
|
+
}
|
|
133
|
+
assign_nearest(mat, med, assi);
|
|
134
|
+
return [sil, iter, n_swaps];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Not exposed. Use pamsil_build or pamsil.
|
|
139
|
+
* Standard PAM BUILD storing nearest distance per point in a plain number array `data`.
|
|
140
|
+
* @returns {number} loss
|
|
141
|
+
*/
|
|
142
|
+
function pamsil_build_initialize(mat, meds, assi, k) {
|
|
143
|
+
const n = mat.len();
|
|
144
|
+
// choose first medoid
|
|
145
|
+
let best = [0, k];
|
|
146
|
+
for (let i = 0; i < n; i++) {
|
|
147
|
+
let sum = 0;
|
|
148
|
+
for (let j = 0; j < n; j++) {
|
|
149
|
+
if (j !== i) {
|
|
150
|
+
sum += mat.get(j, i);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (i === 0 || sum < best[0]) {
|
|
154
|
+
best = [sum, i];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
let loss = best[0];
|
|
158
|
+
meds.push(best[1]);
|
|
159
|
+
const data = [];
|
|
160
|
+
assi.fill(0);
|
|
161
|
+
for (let j = 0; j < n; j++) {
|
|
162
|
+
data.push(mat.get(j, best[1]));
|
|
163
|
+
}
|
|
164
|
+
// choose remaining medoids
|
|
165
|
+
for (let _ = 1; _ < k; _++) {
|
|
166
|
+
best = [0, k];
|
|
167
|
+
for (let i = 0; i < data.length; i++) {
|
|
168
|
+
const di = data[i];
|
|
169
|
+
let sum = -di;
|
|
170
|
+
for (let j = 0; j < data.length; j++) {
|
|
171
|
+
const dnear = data[j];
|
|
172
|
+
if (j !== i) {
|
|
173
|
+
const d = mat.get(j, i);
|
|
174
|
+
if (d < dnear) {
|
|
175
|
+
sum += d - dnear;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (i === 0 || sum < best[0]) {
|
|
180
|
+
best = [sum, i];
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
if (!(best[0] <= 0)) throw new Error('assertion failed: best.0 <= L::zero()');
|
|
184
|
+
// Update assignments:
|
|
185
|
+
loss = 0;
|
|
186
|
+
for (let j = 0; j < data.length; j++) {
|
|
187
|
+
if (j === best[1]) {
|
|
188
|
+
data[j] = 0;
|
|
189
|
+
continue;
|
|
190
|
+
}
|
|
191
|
+
const dj = mat.get(j, best[1]);
|
|
192
|
+
if (dj < data[j]) {
|
|
193
|
+
data[j] = dj;
|
|
194
|
+
}
|
|
195
|
+
loss += data[j];
|
|
196
|
+
}
|
|
197
|
+
meds.push(best[1]);
|
|
198
|
+
}
|
|
199
|
+
return loss;
|
|
200
|
+
}
|
|
201
|
+
</code></pre>
|
|
202
|
+
</article>
|
|
203
|
+
</section>
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
</div>
|
|
211
|
+
|
|
212
|
+
<br class="clear">
|
|
213
|
+
|
|
214
|
+
<footer>
|
|
215
|
+
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.5</a> on Sun Jun 14 2026 10:59:21 GMT+0800 (台北標準時間) using the <a href="https://github.com/clenemt/docdash">docdash</a> theme.
|
|
216
|
+
</footer>
|
|
217
|
+
|
|
218
|
+
<script>prettyPrint();</script>
|
|
219
|
+
<script src="scripts/polyfill.js"></script>
|
|
220
|
+
<script src="scripts/linenumber.js"></script>
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
</body>
|
|
225
|
+
</html>
|