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,305 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<title>k-medoids/fasterpam.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/fasterpam.mjs</h1>
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
<section>
|
|
47
|
+
<article>
|
|
48
|
+
<pre class="prettyprint source linenums"><code>// FasterPAM algorithm, ported 1:1 from src/fasterpam.rs
|
|
49
|
+
import { arrayAdapter } from './arrayadapter.mjs';
|
|
50
|
+
import { Rec, DistancePair, U32_MAX, find_min, choose_medoid_within_partition } from './util.mjs';
|
|
51
|
+
import { shuffle } from './initialization.mjs';
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Run the FasterPAM algorithm.
|
|
55
|
+
*
|
|
56
|
+
* If used multiple times, it is better to additionally shuffle the input data,
|
|
57
|
+
* to increase randomness of the solutions found and hence increase the chance
|
|
58
|
+
* of finding a better solution.
|
|
59
|
+
*
|
|
60
|
+
* @param mat - a pairwise distance matrix
|
|
61
|
+
* @param med - the list of medoids (mutated in place)
|
|
62
|
+
* @param maxiter - the maximum number of iterations allowed
|
|
63
|
+
* @returns { loss, assi, nIter, nSwaps }
|
|
64
|
+
*
|
|
65
|
+
* Panics (throws) when the dissimilarity matrix is not square, or k is 0 or larger than N.
|
|
66
|
+
*/
|
|
67
|
+
export function fasterpam(mat, med, maxiter) {
|
|
68
|
+
mat = arrayAdapter(mat);
|
|
69
|
+
const n = mat.len(), k = med.length;
|
|
70
|
+
if (k === 1) {
|
|
71
|
+
const assi = new Array(n).fill(0);
|
|
72
|
+
const [swapped, loss] = choose_medoid_within_partition(mat, assi, med, 0);
|
|
73
|
+
return { loss, assi, nIter: 1, nSwaps: swapped ? 1 : 0 };
|
|
74
|
+
}
|
|
75
|
+
let [loss, data] = initial_assignment(mat, med);
|
|
76
|
+
let removal_loss = new Array(k).fill(0);
|
|
77
|
+
update_removal_loss(data, removal_loss);
|
|
78
|
+
let lastswap = n, n_swaps = 0, iter = 0;
|
|
79
|
+
while (iter < maxiter) {
|
|
80
|
+
iter += 1;
|
|
81
|
+
const swaps_before = n_swaps, lastloss = loss;
|
|
82
|
+
for (let j = 0; j < n; j++) {
|
|
83
|
+
if (j === lastswap) {
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
if (j === med[data[j].near.i]) {
|
|
87
|
+
continue; // This already is a medoid
|
|
88
|
+
}
|
|
89
|
+
const [change, b] = find_best_swap(mat, removal_loss, data, j);
|
|
90
|
+
if (change >= 0) {
|
|
91
|
+
continue; // No improvement
|
|
92
|
+
}
|
|
93
|
+
n_swaps += 1;
|
|
94
|
+
lastswap = j;
|
|
95
|
+
// perform the swap
|
|
96
|
+
loss = do_swap(mat, med, data, b, j);
|
|
97
|
+
update_removal_loss(data, removal_loss);
|
|
98
|
+
}
|
|
99
|
+
if (n_swaps === swaps_before || loss >= lastloss) {
|
|
100
|
+
break; // converged
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const assi = data.map((x) => x.near.i);
|
|
104
|
+
return { loss, assi, nIter: iter, nSwaps: n_swaps };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Run the FasterPAM algorithm with additional randomization.
|
|
109
|
+
*
|
|
110
|
+
* This increases the chance of finding a better solution when used multiple times,
|
|
111
|
+
* as it decreases the dependency on the input data order.
|
|
112
|
+
*
|
|
113
|
+
* @param mat - a pairwise distance matrix
|
|
114
|
+
* @param med - the list of medoids (mutated in place)
|
|
115
|
+
* @param maxiter - the maximum number of iterations allowed
|
|
116
|
+
* @param rng - random number generator for shuffling the input data
|
|
117
|
+
* @returns { loss, assi, nIter, nSwaps }
|
|
118
|
+
*/
|
|
119
|
+
export function rand_fasterpam(mat, med, maxiter, rng = Math.random) {
|
|
120
|
+
mat = arrayAdapter(mat);
|
|
121
|
+
const n = mat.len(), k = med.length;
|
|
122
|
+
if (k === 1) {
|
|
123
|
+
const assi = new Array(n).fill(0);
|
|
124
|
+
const [swapped, loss] = choose_medoid_within_partition(mat, assi, med, 0);
|
|
125
|
+
return { loss, assi, nIter: 1, nSwaps: swapped ? 1 : 0 };
|
|
126
|
+
}
|
|
127
|
+
let [loss, data] = initial_assignment(mat, med);
|
|
128
|
+
|
|
129
|
+
let removal_loss = new Array(k).fill(0);
|
|
130
|
+
update_removal_loss(data, removal_loss);
|
|
131
|
+
let lastswap = n, n_swaps = 0, iter = 0;
|
|
132
|
+
const seq = shuffle(rng, n); // random shuffling
|
|
133
|
+
while (iter < maxiter) {
|
|
134
|
+
iter += 1;
|
|
135
|
+
const swaps_before = n_swaps, lastloss = loss;
|
|
136
|
+
for (const j of seq) {
|
|
137
|
+
if (j === lastswap) {
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
if (j === med[data[j].near.i]) {
|
|
141
|
+
continue; // This already is a medoid
|
|
142
|
+
}
|
|
143
|
+
const [change, b] = find_best_swap(mat, removal_loss, data, j);
|
|
144
|
+
if (change >= 0) {
|
|
145
|
+
continue; // No improvement
|
|
146
|
+
}
|
|
147
|
+
n_swaps += 1;
|
|
148
|
+
lastswap = j;
|
|
149
|
+
// perform the swap
|
|
150
|
+
loss = do_swap(mat, med, data, b, j);
|
|
151
|
+
update_removal_loss(data, removal_loss);
|
|
152
|
+
}
|
|
153
|
+
if (n_swaps === swaps_before || loss >= lastloss) {
|
|
154
|
+
break; // converged
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const assi = data.map((x) => x.near.i);
|
|
158
|
+
return { loss, assi, nIter: iter, nSwaps: n_swaps };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/** Perform the initial assignment to medoids. Returns [loss, data] with data = Rec[]. */
|
|
162
|
+
export function initial_assignment(mat, med) {
|
|
163
|
+
const n = mat.len(), k = med.length;
|
|
164
|
+
if (!mat.isSquare()) throw new Error('Dissimilarity matrix is not square');
|
|
165
|
+
if (!(n <= U32_MAX)) throw new Error('N is too large');
|
|
166
|
+
if (!(k > 0 && k < U32_MAX)) throw new Error('invalid N');
|
|
167
|
+
if (!(k <= n)) throw new Error('k must be at most N');
|
|
168
|
+
const data = Array.from({ length: mat.len() }, () => Rec.empty());
|
|
169
|
+
|
|
170
|
+
const firstcenter = med[0];
|
|
171
|
+
let loss = 0;
|
|
172
|
+
for (let i = 0; i < data.length; i++) {
|
|
173
|
+
// *cur = Rec::new(...): overwrite the slot with a fresh Rec, then mutate it
|
|
174
|
+
data[i] = new Rec(0, mat.get(i, firstcenter), U32_MAX, 0);
|
|
175
|
+
const curRec = data[i];
|
|
176
|
+
for (let m = 1; m < med.length; m++) {
|
|
177
|
+
const me = med[m];
|
|
178
|
+
const d = mat.get(i, me);
|
|
179
|
+
if (d < curRec.near.d || i === me) {
|
|
180
|
+
curRec.seco = curRec.near.clone();
|
|
181
|
+
curRec.near = new DistancePair(m, d);
|
|
182
|
+
} else if (curRec.seco.i === U32_MAX || d < curRec.seco.d) {
|
|
183
|
+
curRec.seco = new DistancePair(m, d);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
loss += curRec.near.d;
|
|
187
|
+
}
|
|
188
|
+
return [loss, data];
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** Find the best swap for object j - FastPAM version. Returns [change, b]. */
|
|
192
|
+
export function find_best_swap(mat, removal_loss, data, j) {
|
|
193
|
+
const ploss = removal_loss.slice();
|
|
194
|
+
// Improvement from the journal version:
|
|
195
|
+
let acc = 0;
|
|
196
|
+
for (let o = 0; o < data.length; o++) {
|
|
197
|
+
const reco = data[o];
|
|
198
|
+
const doj = mat.get(o, j);
|
|
199
|
+
// New medoid is closest:
|
|
200
|
+
if (doj < reco.near.d) {
|
|
201
|
+
acc += doj - reco.near.d;
|
|
202
|
+
// loss already includes ds - dn, remove
|
|
203
|
+
ploss[reco.near.i] += reco.near.d - reco.seco.d;
|
|
204
|
+
} else if (doj < reco.seco.d) {
|
|
205
|
+
// loss already includes ds - dn, adjust to d(xo) - dn
|
|
206
|
+
ploss[reco.near.i] += doj - reco.seco.d;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
const [b, bloss] = find_min(ploss);
|
|
210
|
+
return [bloss + acc, b]; // add the shared accumulator
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** Update the loss when removing each medoid. Mutates lossArr. */
|
|
214
|
+
export function update_removal_loss(data, loss) {
|
|
215
|
+
loss.fill(0); // stable since 1.50
|
|
216
|
+
for (let r = 0; r < data.length; r++) {
|
|
217
|
+
const rec = data[r];
|
|
218
|
+
loss[rec.near.i] += rec.seco.d - rec.near.d;
|
|
219
|
+
// as N might be unsigned
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Update the second nearest medoid information. Called after each swap. Returns a fresh DistancePair. */
|
|
224
|
+
export function update_second_nearest(mat, med, n, b, o, doj) {
|
|
225
|
+
let s = new DistancePair(b, doj);
|
|
226
|
+
for (let i = 0; i < med.length; i++) {
|
|
227
|
+
const mi = med[i];
|
|
228
|
+
if (i === n || i === b) {
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
const d = mat.get(o, mi);
|
|
232
|
+
if (d < s.d) {
|
|
233
|
+
s = new DistancePair(i, d);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
return s;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/** Perform a single swap. Returns the RAW summed loss (sum of near.d). */
|
|
240
|
+
export function do_swap(mat, med, data, b, j) {
|
|
241
|
+
const n = mat.len();
|
|
242
|
+
if (!(b < med.length)) throw new Error('invalid medoid number');
|
|
243
|
+
if (!(j < n)) throw new Error('invalid object number');
|
|
244
|
+
med[b] = j;
|
|
245
|
+
let acc = 0;
|
|
246
|
+
for (let o = 0; o < data.length; o++) {
|
|
247
|
+
const reco = data[o];
|
|
248
|
+
if (o === j) {
|
|
249
|
+
if (reco.near.i !== b) {
|
|
250
|
+
reco.seco = reco.near.clone();
|
|
251
|
+
}
|
|
252
|
+
reco.near = new DistancePair(b, 0);
|
|
253
|
+
acc += 0;
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
const doj = mat.get(o, j);
|
|
257
|
+
// Nearest medoid is gone:
|
|
258
|
+
if (reco.near.i === b) {
|
|
259
|
+
if (doj < reco.seco.d) {
|
|
260
|
+
reco.near = new DistancePair(b, doj);
|
|
261
|
+
} else {
|
|
262
|
+
reco.near = reco.seco.clone();
|
|
263
|
+
reco.seco = update_second_nearest(mat, med, reco.near.i, b, o, doj);
|
|
264
|
+
}
|
|
265
|
+
} else {
|
|
266
|
+
// nearest not removed
|
|
267
|
+
if (doj < reco.near.d) {
|
|
268
|
+
reco.seco = reco.near.clone();
|
|
269
|
+
reco.near = new DistancePair(b, doj);
|
|
270
|
+
} else if (doj < reco.seco.d) {
|
|
271
|
+
reco.seco = new DistancePair(b, doj);
|
|
272
|
+
} else if (reco.seco.i === b) {
|
|
273
|
+
// second nearest was replaced
|
|
274
|
+
reco.seco = update_second_nearest(mat, med, reco.near.i, b, o, doj);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
acc += reco.near.d;
|
|
278
|
+
}
|
|
279
|
+
return acc;
|
|
280
|
+
}
|
|
281
|
+
</code></pre>
|
|
282
|
+
</article>
|
|
283
|
+
</section>
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
|
|
290
|
+
</div>
|
|
291
|
+
|
|
292
|
+
<br class="clear">
|
|
293
|
+
|
|
294
|
+
<footer>
|
|
295
|
+
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.
|
|
296
|
+
</footer>
|
|
297
|
+
|
|
298
|
+
<script>prettyPrint();</script>
|
|
299
|
+
<script src="scripts/polyfill.js"></script>
|
|
300
|
+
<script src="scripts/linenumber.js"></script>
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
</body>
|
|
305
|
+
</html>
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<title>k-medoids/fastmsc.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/fastmsc.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/fastmsc.rs (FastMSC algorithm).
|
|
49
|
+
import { arrayAdapter } from './arrayadapter.mjs';
|
|
50
|
+
import { USIZE_MAX, choose_medoid_within_partition } from './util.mjs';
|
|
51
|
+
import { initial_assignment, update_removal_loss, find_best_swap, do_swap, initial_assignment_k2, find_best_swap_k2, do_swap_k2 } from './fastermsc.mjs';
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Run the FastMSC algorithm, which yields the same results as the original PAMMEDSIL.
|
|
55
|
+
*
|
|
56
|
+
* This is faster than PAMMEDSIL, but slower than FasterMSC, and mostly of interest for academic reasons.
|
|
57
|
+
* This is the improved version, which costs O(n^2) per iteration to find the best swap.
|
|
58
|
+
*
|
|
59
|
+
* @param {object} 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, assi, nIter, nSwaps }
|
|
63
|
+
*/
|
|
64
|
+
export function fastmsc(mat, med, maxiter) {
|
|
65
|
+
mat = arrayAdapter(mat);
|
|
66
|
+
const n = mat.len(), k = med.length;
|
|
67
|
+
if (k === 1) {
|
|
68
|
+
const assi = new Array(n).fill(0);
|
|
69
|
+
const [swapped, loss] = choose_medoid_within_partition(mat, assi, med, 0);
|
|
70
|
+
return { loss, assi, nIter: 1, nSwaps: swapped ? 1 : 0 };
|
|
71
|
+
}
|
|
72
|
+
if (k === 2) { // special hadling, as there is no third
|
|
73
|
+
return fastmsc_k2(mat, med, maxiter);
|
|
74
|
+
}
|
|
75
|
+
let [loss, data] = initial_assignment(mat, med);
|
|
76
|
+
|
|
77
|
+
let removal_loss = new Array(k).fill(0);
|
|
78
|
+
let n_swaps = 0, iter = 0;
|
|
79
|
+
while (iter < maxiter) {
|
|
80
|
+
iter += 1;
|
|
81
|
+
let best = [0, USIZE_MAX, USIZE_MAX];
|
|
82
|
+
update_removal_loss(data, removal_loss);
|
|
83
|
+
for (let j = 0; j < n; j++) {
|
|
84
|
+
if (j === med[data[j].near.i]) {
|
|
85
|
+
continue; // This already is a medoid
|
|
86
|
+
}
|
|
87
|
+
const [change, b] = find_best_swap(mat, removal_loss, data, j);
|
|
88
|
+
if (change <= best[0]) {
|
|
89
|
+
continue; // No improvement
|
|
90
|
+
}
|
|
91
|
+
best = [change, b, j];
|
|
92
|
+
}
|
|
93
|
+
if (best[0] > 0) {
|
|
94
|
+
n_swaps += 1;
|
|
95
|
+
// perform the swap
|
|
96
|
+
const newloss = do_swap(mat, med, data, best[1], best[2]);
|
|
97
|
+
if (newloss >= loss) {
|
|
98
|
+
break; // Probably numerically unstable now.
|
|
99
|
+
}
|
|
100
|
+
loss = newloss;
|
|
101
|
+
} else {
|
|
102
|
+
break; // No improvement, or NaN.
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const assi = data.map((x) => x.near.i);
|
|
106
|
+
loss = 1 - loss / n;
|
|
107
|
+
return { loss, assi, nIter: iter, nSwaps: n_swaps };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/** Special case k=2 of the FastMSC algorithm. */
|
|
111
|
+
function fastmsc_k2(mat, med, maxiter) {
|
|
112
|
+
const n = mat.len(), k = med.length;
|
|
113
|
+
if (!(k === 2)) throw new Error('Only valid for k=2');
|
|
114
|
+
let [loss, assi, data] = initial_assignment_k2(mat, med);
|
|
115
|
+
let n_swaps = 0, iter = 0;
|
|
116
|
+
while (iter < maxiter) {
|
|
117
|
+
iter += 1;
|
|
118
|
+
let best = [0, k, USIZE_MAX];
|
|
119
|
+
for (let j = 0; j < n; j++) {
|
|
120
|
+
if (j === med[assi[j]]) {
|
|
121
|
+
continue; // This already is a medoid
|
|
122
|
+
}
|
|
123
|
+
const [newloss, b] = find_best_swap_k2(mat, data, j); // assi not used, see below
|
|
124
|
+
if (best[2] === USIZE_MAX || newloss < best[0]) {
|
|
125
|
+
best = [newloss, b, j];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
if (!(best[0] < loss)) {
|
|
129
|
+
break; // No improvement
|
|
130
|
+
}
|
|
131
|
+
// perform the swap
|
|
132
|
+
n_swaps += 1;
|
|
133
|
+
const newloss = do_swap_k2(mat, med, assi, data, best[1], best[2]);
|
|
134
|
+
if (!(newloss < loss)) {
|
|
135
|
+
break; // Probably numerically unstable
|
|
136
|
+
}
|
|
137
|
+
loss = newloss;
|
|
138
|
+
}
|
|
139
|
+
loss = 1 - loss / n;
|
|
140
|
+
return { loss, assi, nIter: iter, nSwaps: n_swaps };
|
|
141
|
+
}
|
|
142
|
+
</code></pre>
|
|
143
|
+
</article>
|
|
144
|
+
</section>
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
</div>
|
|
152
|
+
|
|
153
|
+
<br class="clear">
|
|
154
|
+
|
|
155
|
+
<footer>
|
|
156
|
+
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.
|
|
157
|
+
</footer>
|
|
158
|
+
|
|
159
|
+
<script>prettyPrint();</script>
|
|
160
|
+
<script src="scripts/polyfill.js"></script>
|
|
161
|
+
<script src="scripts/linenumber.js"></script>
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
</body>
|
|
166
|
+
</html>
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<title>k-medoids/fastpam1.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/fastpam1.mjs</h1>
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
<section>
|
|
47
|
+
<article>
|
|
48
|
+
<pre class="prettyprint source linenums"><code>import { arrayAdapter } from './arrayadapter.mjs';
|
|
49
|
+
import { USIZE_MAX, choose_medoid_within_partition } from './util.mjs';
|
|
50
|
+
import { initial_assignment, find_best_swap, do_swap, update_removal_loss } from './fasterpam.mjs';
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Run the FastPAM1 algorithm, which yields the same results as the original PAM.
|
|
54
|
+
*
|
|
55
|
+
* This is faster than PAM, but slower than FasterPAM, and mostly of interest for academic reasons.
|
|
56
|
+
* Quality-wise, FasterPAM is not worse on average, but much faster.
|
|
57
|
+
*
|
|
58
|
+
* This is the improved version from the journal version of the paper,
|
|
59
|
+
* which costs O(n²) per iteration to find the best swap.
|
|
60
|
+
*
|
|
61
|
+
* @param {object} mat - a pairwise distance matrix (array or wrapped)
|
|
62
|
+
* @param {number[]} med - the list of medoids (mutated in place)
|
|
63
|
+
* @param {number} maxiter - the maximum number of iterations allowed
|
|
64
|
+
* @returns {{ loss: number, assi: number[], nIter: number, nSwaps: number }}
|
|
65
|
+
*/
|
|
66
|
+
export function fastpam1(mat, med, maxiter) {
|
|
67
|
+
mat = arrayAdapter(mat);
|
|
68
|
+
const n = mat.len();
|
|
69
|
+
const k = med.length;
|
|
70
|
+
if (k === 1) {
|
|
71
|
+
const assi = new Array(n).fill(0);
|
|
72
|
+
const [swapped, loss] = choose_medoid_within_partition(mat, assi, med, 0);
|
|
73
|
+
return { loss, assi, nIter: 1, nSwaps: swapped ? 1 : 0 };
|
|
74
|
+
}
|
|
75
|
+
let [loss, data] = initial_assignment(mat, med);
|
|
76
|
+
const removal_loss = new Array(k).fill(0);
|
|
77
|
+
let n_swaps = 0;
|
|
78
|
+
let iter = 0;
|
|
79
|
+
while (iter < maxiter) {
|
|
80
|
+
iter += 1;
|
|
81
|
+
let best = [0, USIZE_MAX, USIZE_MAX];
|
|
82
|
+
update_removal_loss(data, removal_loss);
|
|
83
|
+
for (let j = 0; j < n; j++) {
|
|
84
|
+
if (j === med[data[j].near.i]) {
|
|
85
|
+
continue; // This already is a medoid
|
|
86
|
+
}
|
|
87
|
+
const [change, b] = find_best_swap(mat, removal_loss, data, j);
|
|
88
|
+
if (change >= best[0]) {
|
|
89
|
+
continue; // No improvement
|
|
90
|
+
}
|
|
91
|
+
best = [change, b, j];
|
|
92
|
+
}
|
|
93
|
+
if (best[0] < 0) {
|
|
94
|
+
n_swaps += 1;
|
|
95
|
+
// perform the swap
|
|
96
|
+
const newloss = do_swap(mat, med, data, best[1], best[2]);
|
|
97
|
+
if (newloss >= loss) {
|
|
98
|
+
break; // Probably numerically unstable now.
|
|
99
|
+
}
|
|
100
|
+
loss = newloss;
|
|
101
|
+
} else {
|
|
102
|
+
break; // No improvement, or NaN.
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const assi = data.map(x => x.near.i);
|
|
106
|
+
return { loss, assi, nIter: iter, nSwaps: n_swaps };
|
|
107
|
+
}
|
|
108
|
+
</code></pre>
|
|
109
|
+
</article>
|
|
110
|
+
</section>
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<br class="clear">
|
|
120
|
+
|
|
121
|
+
<footer>
|
|
122
|
+
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.
|
|
123
|
+
</footer>
|
|
124
|
+
|
|
125
|
+
<script>prettyPrint();</script>
|
|
126
|
+
<script src="scripts/polyfill.js"></script>
|
|
127
|
+
<script src="scripts/linenumber.js"></script>
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
</body>
|
|
132
|
+
</html>
|