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.
Files changed (72) hide show
  1. package/.github/workflows/ci-test.yml +3 -3
  2. package/README.md +4 -6
  3. package/babel.config.js +4 -3
  4. package/dist/w-cluster.umd.js +2 -2
  5. package/dist/w-cluster.umd.js.map +1 -1
  6. package/dist/w-cluster.wk.umd.js +1 -1
  7. package/docs/DenseMatrix.html +202 -0
  8. package/docs/DistancePair.html +202 -0
  9. package/docs/LowerTriangle.html +202 -0
  10. package/docs/Rec.html +202 -0
  11. package/docs/Reco.html +202 -0
  12. package/docs/WCluster.mjs.html +8 -4
  13. package/docs/examples/ex-PCA.html +1 -1
  14. package/docs/examples/ex-cluster-webworker.html +1 -1
  15. package/docs/examples/ex-cluster.html +1 -1
  16. package/docs/global.html +6799 -164
  17. package/docs/index.html +2 -2
  18. package/docs/k-medoids_alternating.mjs.html +144 -0
  19. package/docs/k-medoids_arrayadapter.mjs.html +107 -0
  20. package/docs/k-medoids_dynmsc.mjs.html +255 -0
  21. package/docs/k-medoids_fastermsc.mjs.html +411 -0
  22. package/docs/k-medoids_fasterpam.mjs.html +305 -0
  23. package/docs/k-medoids_fastmsc.mjs.html +166 -0
  24. package/docs/k-medoids_fastpam1.mjs.html +132 -0
  25. package/docs/k-medoids_initialization.mjs.html +107 -0
  26. package/docs/k-medoids_pam.mjs.html +275 -0
  27. package/docs/k-medoids_pammedsil.mjs.html +307 -0
  28. package/docs/k-medoids_pamsil.mjs.html +225 -0
  29. package/docs/k-medoids_par_fasterpam.mjs.html +134 -0
  30. package/docs/k-medoids_par_silhouette.mjs.html +126 -0
  31. package/docs/k-medoids_silhouette.mjs.html +193 -0
  32. package/docs/k-medoids_util.mjs.html +135 -0
  33. package/g-PCA-nodeworker.mjs +1 -1
  34. package/g-PCA.mjs +1 -1
  35. package/g-cluster-kMeans-large.mjs +83 -0
  36. package/g-cluster-kMeans-nodeworker.mjs +181 -0
  37. package/g-cluster-kMeans.mjs +178 -0
  38. package/g-cluster-kMedoids-large-suggest.mjs +95 -0
  39. package/g-cluster-kMedoids-large.mjs +104 -0
  40. package/{g-cluster-nodeworker.mjs → g-cluster-kMedoids-nodeworker.mjs} +1 -2
  41. package/{g-cluster.mjs → g-cluster-kMedoids-simple.mjs} +1 -1
  42. package/package.json +7 -7
  43. package/script.txt +5 -7
  44. package/src/WCluster.mjs +6 -2
  45. package/src/WClusterCore.mjs +30 -21
  46. package/src/WClusterMat.mjs +98 -24
  47. package/src/jaccardBitset.mjs +63 -0
  48. package/src/k-medoids/README.md +124 -0
  49. package/src/k-medoids/alternating.mjs +72 -0
  50. package/src/k-medoids/arrayadapter.mjs +35 -0
  51. package/src/k-medoids/dynmsc.mjs +183 -0
  52. package/src/k-medoids/fastermsc.mjs +339 -0
  53. package/src/k-medoids/fasterpam.mjs +233 -0
  54. package/src/k-medoids/fastmsc.mjs +94 -0
  55. package/src/k-medoids/fastpam1.mjs +60 -0
  56. package/src/k-medoids/index.mjs +37 -0
  57. package/src/k-medoids/initialization.mjs +35 -0
  58. package/src/k-medoids/package.json +8 -0
  59. package/src/k-medoids/pam.mjs +203 -0
  60. package/src/k-medoids/pammedsil.mjs +235 -0
  61. package/src/k-medoids/pamsil.mjs +153 -0
  62. package/src/k-medoids/par_fasterpam.mjs +62 -0
  63. package/src/k-medoids/par_silhouette.mjs +54 -0
  64. package/src/k-medoids/silhouette.mjs +121 -0
  65. package/src/k-medoids/test.mjs +357 -0
  66. package/src/k-medoids/util.mjs +63 -0
  67. package/test/jaccardBitset.test.mjs +141 -0
  68. package/test/kMeans.test.mjs +278 -0
  69. package/test/kMedoids-large-suggest.test.mjs +140 -0
  70. package/test/kMedoids-large.test.mjs +142 -0
  71. package/test/{cluster.test.mjs → kMedoids-simple.test.mjs} +5 -5
  72. package/toolg/gDocsExams.mjs +1 -1
@@ -0,0 +1,411 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+
5
+ <meta charset="utf-8">
6
+ <title>k-medoids/fastermsc.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/fastermsc.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/fastermsc.rs (FasterMSC algorithm).
49
+ import { arrayAdapter } from './arrayadapter.mjs';
50
+ import { Reco, DistancePair, U32_MAX, find_min, find_max, choose_medoid_within_partition } from './util.mjs';
51
+
52
+ // _loss(a, b): 0 if a or b is zero, else a / b.
53
+ export function _loss(a, b) {
54
+ if (a === 0 || b === 0) { return 0; } else { return a / b; }
55
+ }
56
+
57
+ /**
58
+ * Run the FasterMSC algorithm.
59
+ * @param {object} mat - pairwise distance matrix (wrapped)
60
+ * @param {number[]} med - the list of medoids (mutated in place)
61
+ * @param {number} maxiter - maximum number of iterations
62
+ * returns { loss, assi, nIter, nSwaps }
63
+ */
64
+ export function fastermsc(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 fastermsc_k2(mat, med, maxiter);
74
+ }
75
+ let [loss, data] = initial_assignment(mat, med);
76
+
77
+ let removal_loss = new Array(k).fill(0);
78
+ update_removal_loss(data, removal_loss);
79
+ let lastswap = n, n_swaps = 0, iter = 0;
80
+ while (iter &lt; maxiter) {
81
+ iter += 1;
82
+ const swaps_before = n_swaps, lastloss = loss;
83
+ for (let j = 0; j &lt; n; j++) {
84
+ if (j === lastswap) {
85
+ break;
86
+ }
87
+ if (j === med[data[j].near.i]) {
88
+ continue; // This already is a medoid
89
+ }
90
+ const [change, b] = find_best_swap(mat, removal_loss, data, j);
91
+ if (change &lt;= 0) {
92
+ continue; // No improvement
93
+ }
94
+ n_swaps += 1;
95
+ lastswap = j;
96
+ // perform the swap
97
+ loss = do_swap(mat, med, data, b, j);
98
+ update_removal_loss(data, removal_loss);
99
+ }
100
+ if (n_swaps === swaps_before || loss >= lastloss) {
101
+ break; // converged
102
+ }
103
+ }
104
+ const assi = data.map((x) => x.near.i);
105
+ loss = 1 - loss / n;
106
+ return { loss, assi, nIter: iter, nSwaps: n_swaps };
107
+ }
108
+
109
+ /** Perform the initial assignment to medoids. Returns [loss, data] (data = Reco[]). */
110
+ export function initial_assignment(mat, med) {
111
+ const n = mat.len(), k = med.length;
112
+ if (!mat.isSquare()) throw new Error('Dissimilarity matrix is not square');
113
+ if (!(n &lt;= U32_MAX)) throw new Error('N is too large');
114
+ if (!(k > 0 &amp;&amp; k &lt; U32_MAX)) throw new Error('invalid N');
115
+ if (!(k &lt;= n)) throw new Error('k must be at most N');
116
+ const data = new Array(mat.len());
117
+ for (let _i = 0; _i &lt; data.length; _i++) data[_i] = Reco.empty();
118
+ const firstcenter = med[0];
119
+ let loss = 0;
120
+ for (let i = 0; i &lt; data.length; i++) {
121
+ // Rust: *cur = Reco::new(...) overwrites the slot in place.
122
+ const cur = new Reco(0, mat.get(i, firstcenter), U32_MAX, 0, U32_MAX, 0);
123
+ data[i] = cur;
124
+ for (let m = 1; m &lt; med.length; m++) {
125
+ const me = med[m];
126
+ const d = mat.get(i, me);
127
+ if (d &lt; cur.near.d || i === me) {
128
+ cur.third = cur.seco.clone();
129
+ cur.seco = cur.near.clone();
130
+ cur.near = new DistancePair(m, d);
131
+ } else if (cur.seco.i === U32_MAX || d &lt; cur.seco.d) {
132
+ cur.third = cur.seco.clone();
133
+ cur.seco = new DistancePair(m, d);
134
+ } else if (cur.third.i === U32_MAX || d &lt; cur.third.d) {
135
+ cur.third = new DistancePair(m, d);
136
+ }
137
+ }
138
+ loss += _loss(cur.near.d, cur.seco.d);
139
+ }
140
+ return [loss, data];
141
+ }
142
+
143
+ /** Find the best swap for object j - FastMSC version. Returns [change, b]. */
144
+ export function find_best_swap(mat, removal_loss, data, j) {
145
+ const ploss = removal_loss.slice();
146
+ // Improvement from the journal version:
147
+ let acc = 0;
148
+ for (let o = 0; o &lt; data.length; o++) {
149
+ const reco = data[o];
150
+ const doj = mat.get(o, j);
151
+ if (doj &lt; reco.near.d) {
152
+ acc += _loss(reco.near.d, reco.seco.d) - _loss(doj, reco.near.d);
153
+ // loss already includes (dt - ds) - (ds - dn), remove
154
+ ploss[reco.near.i] += _loss(doj, reco.near.d) + _loss(reco.seco.d, reco.third.d) - _loss(reco.near.d + doj, reco.seco.d);
155
+ ploss[reco.seco.i] += _loss(reco.near.d, reco.third.d) - _loss(reco.near.d, reco.seco.d);
156
+ } else if (doj &lt; reco.seco.d) {
157
+ acc += _loss(reco.near.d, reco.seco.d) - _loss(reco.near.d, doj);
158
+ ploss[reco.near.i] += _loss(reco.near.d, doj) + _loss(reco.seco.d, reco.third.d) - _loss(reco.near.d + doj, reco.seco.d);
159
+ // loss already includes (dt - ds) - (ds - dn), adjust to 2*d(xo) - ds - dt
160
+ // loss already includes (dt - ds), adjust to 2*d(xo) - ds - dt
161
+ ploss[reco.seco.i] += _loss(reco.near.d, reco.third.d) - _loss(reco.near.d, reco.seco.d);
162
+ } else if (doj &lt; reco.third.d) {
163
+ // loss already includes (dt - ds) - (ds - dn), adjust to d(xo)- dt
164
+ ploss[reco.near.i] += _loss(reco.seco.d, reco.third.d) - _loss(reco.seco.d, doj);
165
+ // loss already includes (dt - ds), adjust to d(xo)- dt
166
+ ploss[reco.seco.i] += _loss(reco.near.d, reco.third.d) - _loss(reco.near.d, doj);
167
+ }
168
+ }
169
+ const [b, bloss] = find_max(ploss);
170
+ return [bloss + acc, b]; // add the shared accumulator
171
+ }
172
+
173
+ /** Update the loss when removing each medoid. Mutates loss in place. */
174
+ export function update_removal_loss(data, loss) {
175
+ loss.fill(0); // stable since 1.50
176
+ for (let r = 0; r &lt; data.length; r++) {
177
+ const rec = data[r];
178
+ loss[rec.near.i] += _loss(rec.near.d, rec.seco.d) - _loss(rec.seco.d, rec.third.d);
179
+ loss[rec.seco.i] += _loss(rec.near.d, rec.seco.d) - _loss(rec.near.d, rec.third.d);
180
+ // as N might be unsigned
181
+ }
182
+ }
183
+
184
+ /** Update the third nearest medoid information. Called after each swap. Returns fresh DistancePair. */
185
+ export function update_third_nearest(mat, med, n, s, b, o, doj) {
186
+ let dist = new DistancePair(b, doj);
187
+ for (let i = 0; i &lt; med.length; i++) {
188
+ const mi = med[i];
189
+ if (i === n || i === b || i === s) {
190
+ continue;
191
+ }
192
+ const d = mat.get(o, mi);
193
+ if (d &lt; dist.d) {
194
+ dist = new DistancePair(i, d);
195
+ }
196
+ }
197
+ return dist;
198
+ }
199
+
200
+ /** Perform a single swap. Returns RAW loss. */
201
+ export function do_swap(mat, med, data, b, j) {
202
+ const n = mat.len();
203
+ if (!(b &lt; med.length)) throw new Error('invalid medoid number');
204
+ if (!(j &lt; n)) throw new Error('invalid object number');
205
+ med[b] = j;
206
+ let acc = 0;
207
+ for (let o = 0; o &lt; data.length; o++) {
208
+ const reco = data[o];
209
+ if (o === j) {
210
+ if (reco.near.i !== b) {
211
+ if (reco.seco.i !== b) {
212
+ reco.third = reco.seco.clone();
213
+ }
214
+ reco.seco = reco.near.clone();
215
+ }
216
+ reco.near = new DistancePair(b, 0);
217
+ acc += 0;
218
+ continue;
219
+ }
220
+ const doj = mat.get(o, j);
221
+ // Nearest medoid is gone:
222
+ if (reco.near.i === b) {
223
+ if (doj &lt; reco.seco.d) {
224
+ reco.near = new DistancePair(b, doj);
225
+ } else if (reco.third.i === U32_MAX || doj &lt; reco.third.d) {
226
+ reco.near = reco.seco.clone();
227
+ reco.seco = new DistancePair(b, doj);
228
+ } else {
229
+ reco.near = reco.seco.clone();
230
+ reco.seco = reco.third.clone();
231
+ reco.third = update_third_nearest(mat, med, reco.near.i, reco.seco.i, b, o, doj);
232
+ }
233
+ } else if (reco.seco.i === b) {
234
+ // second nearest was replaced
235
+ if (doj &lt; reco.near.d) {
236
+ reco.seco = reco.near.clone();
237
+ reco.near = new DistancePair(b, doj);
238
+ } else if (reco.third.i === U32_MAX || doj &lt; reco.third.d) {
239
+ reco.seco = new DistancePair(b, doj);
240
+ } else {
241
+ reco.seco = reco.third.clone();
242
+ reco.third = update_third_nearest(mat, med, reco.near.i, reco.seco.i, b, o, doj);
243
+ }
244
+ } else {
245
+ // nearest not removed
246
+ if (doj &lt; reco.near.d) {
247
+ reco.third = reco.seco.clone();
248
+ reco.seco = reco.near.clone();
249
+ reco.near = new DistancePair(b, doj);
250
+ } else if (doj &lt; reco.seco.d) {
251
+ reco.third = reco.seco.clone();
252
+ reco.seco = new DistancePair(b, doj);
253
+ } else if (reco.third.i === U32_MAX || doj &lt; reco.third.d) {
254
+ reco.third = new DistancePair(b, doj);
255
+ } else if (reco.third.i === b) {
256
+ reco.third = update_third_nearest(mat, med, reco.near.i, reco.seco.i, b, o, doj);
257
+ }
258
+ }
259
+ acc += _loss(reco.near.d, reco.seco.d);
260
+ }
261
+ return acc;
262
+ }
263
+
264
+ /** Special case k=2 of the FasterMSC algorithm. Returns { loss, assi, nIter, nSwaps }. */
265
+ export function fastermsc_k2(mat, med, maxiter) {
266
+ const n = mat.len(), k = med.length;
267
+ if (!(k === 2)) throw new Error('Only valid for k=2');
268
+ let [loss, assi, data] = initial_assignment_k2(mat, med);
269
+ let lastswap = n, n_swaps = 0, iter = 0;
270
+ while (iter &lt; maxiter) {
271
+ iter += 1;
272
+ const swaps_before = n_swaps, lastloss = loss;
273
+ for (let j = 0; j &lt; n; j++) {
274
+ if (j === lastswap) {
275
+ break;
276
+ }
277
+ if (j === med[assi[j]]) {
278
+ continue; // This already is a medoid
279
+ }
280
+ const [newloss, b] = find_best_swap_k2(mat, data, j); // assi not used, see below
281
+ if (newloss >= loss) {
282
+ continue; // No improvement
283
+ }
284
+ n_swaps += 1;
285
+ lastswap = j;
286
+ // perform the swap
287
+ loss = do_swap_k2(mat, med, assi, data, b, j);
288
+ }
289
+ if (n_swaps === swaps_before || loss >= lastloss) {
290
+ break; // converged
291
+ }
292
+ }
293
+ loss = 1 - loss / n;
294
+ return { loss, assi, nIter: iter, nSwaps: n_swaps };
295
+ }
296
+
297
+ /** Perform the initial assignment to medoids, for k=2 only. Returns [loss, assi, data] (data = [d0,d1] pairs). */
298
+ export function initial_assignment_k2(mat, med) {
299
+ const n = mat.len(), k = med.length;
300
+ if (!mat.isSquare()) throw new Error('Dissimilarity matrix is not square');
301
+ if (!(n &lt;= U32_MAX)) throw new Error('N is too large');
302
+ if (!(k === 2)) throw new Error('k must be 2');
303
+ const assi = new Array(mat.len()).fill(0);
304
+ const data = new Array(mat.len());
305
+ for (let _i = 0; _i &lt; data.length; _i++) data[_i] = [0, 0];
306
+ let loss = 0;
307
+ for (let i = 0; i &lt; data.length; i++) {
308
+ const d = data[i];
309
+ d[0] = mat.get(i, med[0]);
310
+ d[1] = mat.get(i, med[1]);
311
+ if (d[0] &lt; d[1]) {
312
+ assi[i] = 0;
313
+ loss += _loss(d[0], d[1]); // return
314
+ } else {
315
+ assi[i] = 1;
316
+ loss += _loss(d[1], d[0]); // return
317
+ }
318
+ }
319
+ return [loss, assi, data];
320
+ }
321
+
322
+ /** Find the best swap for object j - FastMSC version, k=2. Returns [loss, b]. */
323
+ export function find_best_swap_k2(mat, data, j) {
324
+ const ploss = [0, 0];
325
+ for (let o = 0; o &lt; data.length; o++) {
326
+ const d = data[o];
327
+ const doj = mat.get(o, j);
328
+ // We do not use the assignment here, because we stored d0/d1 by medoid position, not closeness
329
+ ploss[0] += (doj &lt; d[1]) ? _loss(doj, d[1]) : _loss(d[1], doj);
330
+ ploss[1] += (doj &lt; d[0]) ? _loss(doj, d[0]) : _loss(d[0], doj);
331
+ }
332
+ const [b, bloss] = find_min(ploss);
333
+ return [bloss, b];
334
+ }
335
+
336
+ /** Perform a single swap, k=2. Returns RAW loss. */
337
+ export function do_swap_k2(mat, med, assi, data, b, j) {
338
+ const n = mat.len();
339
+ if (!(b &lt; med.length)) throw new Error('invalid medoid number');
340
+ if (!(j &lt; n)) throw new Error('invalid object number');
341
+ med[b] = j;
342
+ // Its nicer to have the if outside, even though this looks duplicated
343
+ if (b === 0) {
344
+ let acc = 0;
345
+ for (let o = 0; o &lt; data.length; o++) {
346
+ const d = data[o];
347
+ if (o === j) {
348
+ assi[o] = 0;
349
+ d[0] = 0;
350
+ acc += 0;
351
+ continue;
352
+ }
353
+ const doj = mat.get(o, j);
354
+ d[0] = doj;
355
+ if (doj &lt; d[1] || (doj === d[1] &amp;&amp; assi[o] === 0)) {
356
+ assi[o] = 0;
357
+ acc += _loss(doj, d[1]); // return
358
+ } else {
359
+ assi[o] = 1;
360
+ acc += _loss(d[1], doj); // return
361
+ }
362
+ }
363
+ return acc;
364
+ } else { // b == 1
365
+ let acc = 0;
366
+ for (let o = 0; o &lt; data.length; o++) {
367
+ const d = data[o];
368
+ if (o === j) {
369
+ assi[o] = 1;
370
+ d[1] = 0;
371
+ acc += 0;
372
+ continue;
373
+ }
374
+ const doj = mat.get(o, j);
375
+ d[1] = doj;
376
+ if (doj &lt; d[0] || (doj === d[0] &amp;&amp; assi[o] === 1)) {
377
+ assi[o] = 1;
378
+ acc += _loss(doj, d[0]); // return
379
+ } else {
380
+ assi[o] = 0;
381
+ acc += _loss(d[0], doj); // return
382
+ }
383
+ }
384
+ return acc;
385
+ }
386
+ }
387
+ </code></pre>
388
+ </article>
389
+ </section>
390
+
391
+
392
+
393
+
394
+
395
+
396
+ </div>
397
+
398
+ <br class="clear">
399
+
400
+ <footer>
401
+ 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.
402
+ </footer>
403
+
404
+ <script>prettyPrint();</script>
405
+ <script src="scripts/polyfill.js"></script>
406
+ <script src="scripts/linenumber.js"></script>
407
+
408
+
409
+
410
+ </body>
411
+ </html>