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
package/docs/index.html
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
<nav >
|
|
30
30
|
|
|
31
31
|
|
|
32
|
-
<h2><a href="index.html">Home</a></h2><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#cluster">cluster</a></li></ul>
|
|
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
33
|
|
|
34
34
|
</nav>
|
|
35
35
|
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
<br class="clear">
|
|
72
72
|
|
|
73
73
|
<footer>
|
|
74
|
-
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 4.0.
|
|
74
|
+
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.
|
|
75
75
|
</footer>
|
|
76
76
|
|
|
77
77
|
<script>prettyPrint();</script>
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<title>k-medoids/alternating.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/alternating.mjs</h1>
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
<section>
|
|
47
|
+
<article>
|
|
48
|
+
<pre class="prettyprint source linenums"><code>// Alternating (k-means-style) k-medoids algorithm.
|
|
49
|
+
// Ported 1:1 from src/alternating.rs
|
|
50
|
+
|
|
51
|
+
import { arrayAdapter } from './arrayadapter.mjs';
|
|
52
|
+
import { USIZE_MAX, choose_medoid_within_partition } from './util.mjs';
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Run the Alternating algorithm, a k-means-style alternate optimization.
|
|
56
|
+
*
|
|
57
|
+
* This is fairly fast (O(n²), like the FasterPAM method), but because the
|
|
58
|
+
* newly chosen medoid must cover the entire existing cluster, it tends to
|
|
59
|
+
* get stuck in worse local optima as the alternatives. Hence, it is not
|
|
60
|
+
* really recommended to use this algorithm (also known as "Alternate" in
|
|
61
|
+
* classic facility location literature, and re-invented by Park and Jun 2009).
|
|
62
|
+
*
|
|
63
|
+
* @param {*} mat - a pairwise distance matrix (2D array or adapter object)
|
|
64
|
+
* @param {number[]} med - the list of medoids (mutated in place)
|
|
65
|
+
* @param {number} maxiter - the maximum number of iterations allowed
|
|
66
|
+
* @returns {{ loss: number, assi: number[], nIter: number }}
|
|
67
|
+
*/
|
|
68
|
+
export function alternating(mat, med, maxiter) {
|
|
69
|
+
mat = arrayAdapter(mat);
|
|
70
|
+
const n = mat.len();
|
|
71
|
+
const assi = new Array(n).fill(USIZE_MAX);
|
|
72
|
+
let loss = assign_nearest(mat, med, assi);
|
|
73
|
+
let iter = 0;
|
|
74
|
+
while (iter < maxiter) {
|
|
75
|
+
iter += 1;
|
|
76
|
+
let changed = false;
|
|
77
|
+
for (let i = 0; i < med.length; i++) {
|
|
78
|
+
changed |= choose_medoid_within_partition(mat, assi, med, i)[0];
|
|
79
|
+
}
|
|
80
|
+
if (!changed) {
|
|
81
|
+
break;
|
|
82
|
+
}
|
|
83
|
+
loss = assign_nearest(mat, med, assi);
|
|
84
|
+
}
|
|
85
|
+
return { loss, assi, nIter: iter };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Assign each point to the nearest medoid, return total loss.
|
|
90
|
+
* Mutates dataArr[i] = index into med of nearest medoid.
|
|
91
|
+
*
|
|
92
|
+
* @param {*} mat - already-wrapped matrix
|
|
93
|
+
* @param {number[]} med - medoid point indices
|
|
94
|
+
* @param {number[]} dataArr - assignment array, mutated in place
|
|
95
|
+
* @returns {number} total loss
|
|
96
|
+
*/
|
|
97
|
+
export function assign_nearest(mat, med, dataArr) {
|
|
98
|
+
const n = mat.len();
|
|
99
|
+
const k = med.length;
|
|
100
|
+
if (!mat.isSquare()) throw new Error('Dissimilarity matrix is not square');
|
|
101
|
+
if (n > 4294967295) throw new Error('N is too large');
|
|
102
|
+
if (!(k > 0 && k < 4294967295)) throw new Error('invalid N');
|
|
103
|
+
if (k > n) throw new Error('k must be at most N');
|
|
104
|
+
const firstcenter = med[0];
|
|
105
|
+
let acc = 0;
|
|
106
|
+
for (let i = 0; i < n; i++) {
|
|
107
|
+
let best = [0, mat.get(i, firstcenter)];
|
|
108
|
+
for (let m = 1; m < k; m++) {
|
|
109
|
+
const mm = med[m];
|
|
110
|
+
const dm = mat.get(i, mm);
|
|
111
|
+
if (dm < best[1] || i === mm) {
|
|
112
|
+
best = [m, dm];
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
dataArr[i] = best[0];
|
|
116
|
+
acc += best[1];
|
|
117
|
+
}
|
|
118
|
+
return acc;
|
|
119
|
+
}
|
|
120
|
+
</code></pre>
|
|
121
|
+
</article>
|
|
122
|
+
</section>
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<br class="clear">
|
|
132
|
+
|
|
133
|
+
<footer>
|
|
134
|
+
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.
|
|
135
|
+
</footer>
|
|
136
|
+
|
|
137
|
+
<script>prettyPrint();</script>
|
|
138
|
+
<script src="scripts/polyfill.js"></script>
|
|
139
|
+
<script src="scripts/linenumber.js"></script>
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
</body>
|
|
144
|
+
</html>
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<title>k-medoids/arrayadapter.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/arrayadapter.mjs</h1>
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
<section>
|
|
47
|
+
<article>
|
|
48
|
+
<pre class="prettyprint source linenums"><code>// Array adapters, ported from src/arrayadapter.rs
|
|
49
|
+
// All matrices expose: len() -> n, isSquare() -> bool, get(x, y) -> number
|
|
50
|
+
|
|
51
|
+
/** Wraps a dense 2D array (array of equal-length rows). */
|
|
52
|
+
export class DenseMatrix {
|
|
53
|
+
constructor(rows) { this.rows = rows; }
|
|
54
|
+
len() { return this.rows.length; }
|
|
55
|
+
isSquare() {
|
|
56
|
+
const n = this.rows.length;
|
|
57
|
+
for (let i = 0; i < n; i++) { if (this.rows[i].length !== n) return false; }
|
|
58
|
+
return true;
|
|
59
|
+
}
|
|
60
|
+
get(x, y) { return this.rows[x][y]; }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** Lower triangular matrix in serial form (without diagonal). data length = n*(n-1)/2. */
|
|
64
|
+
export class LowerTriangle {
|
|
65
|
+
constructor(n, data) { this.n = n; this.data = data; }
|
|
66
|
+
len() { return this.n; }
|
|
67
|
+
isSquare() { return this.data.length === ((this.n * (this.n - 1)) / 2); }
|
|
68
|
+
get(x, y) {
|
|
69
|
+
// products x*(x-1) are always even, so /2 is an exact integer (valid up to 2^53);
|
|
70
|
+
// the original >>1 truncates to int32 and overflows the index for n >= ~46342
|
|
71
|
+
if (x < y) return this.data[((y * (y - 1)) / 2) + x];
|
|
72
|
+
if (x > y) return this.data[((x * (x - 1)) / 2) + y];
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** Normalize user input into a matrix object. Idempotent. Accepts a 2D array, DenseMatrix, or LowerTriangle. */
|
|
78
|
+
export function arrayAdapter(input) {
|
|
79
|
+
if (input && typeof input.get === 'function' && typeof input.len === 'function') return input;
|
|
80
|
+
if (Array.isArray(input)) return new DenseMatrix(input);
|
|
81
|
+
throw new Error('Unsupported matrix input: provide a 2D array, a DenseMatrix, or a LowerTriangle');
|
|
82
|
+
}
|
|
83
|
+
</code></pre>
|
|
84
|
+
</article>
|
|
85
|
+
</section>
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
<br class="clear">
|
|
95
|
+
|
|
96
|
+
<footer>
|
|
97
|
+
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.
|
|
98
|
+
</footer>
|
|
99
|
+
|
|
100
|
+
<script>prettyPrint();</script>
|
|
101
|
+
<script src="scripts/polyfill.js"></script>
|
|
102
|
+
<script src="scripts/linenumber.js"></script>
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
</body>
|
|
107
|
+
</html>
|
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<title>k-medoids/dynmsc.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/dynmsc.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/dynmsc.rs (DynMSC 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, update_removal_loss, find_best_swap, do_swap, fastermsc_k2 } from './fastermsc.mjs';
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Run the DynMSC algorithm.
|
|
55
|
+
*
|
|
56
|
+
* We begin with a maximum number of clusters, optimize the Average Medoid Silhouette,
|
|
57
|
+
* then decrease the number of clusters by one, and repeat until we have reached a
|
|
58
|
+
* minimum number of clusters. During this process, we store the solution with the
|
|
59
|
+
* highest AMS to return later.
|
|
60
|
+
*
|
|
61
|
+
* @param {object} mat - a pairwise distance matrix
|
|
62
|
+
* @param {number[]} med - the list of medoids
|
|
63
|
+
* @param {number} mink - the minimum number of clusters
|
|
64
|
+
* @param {number} maxiter - the maximum number of iterations allowed
|
|
65
|
+
* returns { loss, assi, nIter, nSwaps, meds, losses }
|
|
66
|
+
*/
|
|
67
|
+
export function dynmsc(mat, med, mink, maxiter) {
|
|
68
|
+
mat = arrayAdapter(mat);
|
|
69
|
+
med = med.slice(); // Rust: let mut med = med.to_owned();
|
|
70
|
+
const n = mat.len();
|
|
71
|
+
let k = med.length;
|
|
72
|
+
const minimum_k = Math.min(Math.max(mink, 1), k);
|
|
73
|
+
if (k === 1) {
|
|
74
|
+
const return_loss = new Array(1).fill(0);
|
|
75
|
+
const assi = new Array(n).fill(0);
|
|
76
|
+
const [swapped, loss] = choose_medoid_within_partition(mat, assi, med, 0);
|
|
77
|
+
return_loss[0] = loss;
|
|
78
|
+
const return_meds = med.slice();
|
|
79
|
+
return { loss, assi, nIter: 1, nSwaps: swapped ? 1 : 0, meds: return_meds, losses: return_loss };
|
|
80
|
+
}
|
|
81
|
+
let [loss, data] = initial_assignment(mat, med);
|
|
82
|
+
|
|
83
|
+
const return_loss = new Array(k - minimum_k + 1).fill(0);
|
|
84
|
+
let best_loss = 0;
|
|
85
|
+
let return_assi = [0, n]; // Rust: vec![0, n]; overwritten before being meaningfully used
|
|
86
|
+
let return_iter = 0;
|
|
87
|
+
let return_swaps = 0;
|
|
88
|
+
let lastswap, n_swaps, iter;
|
|
89
|
+
let removal_loss = new Array(k).fill(0);
|
|
90
|
+
let return_meds = med.slice();
|
|
91
|
+
while (k >= 3 && k >= minimum_k) {
|
|
92
|
+
update_removal_loss(data, removal_loss);
|
|
93
|
+
lastswap = n;
|
|
94
|
+
n_swaps = 0;
|
|
95
|
+
iter = 0;
|
|
96
|
+
while (iter < maxiter) {
|
|
97
|
+
iter += 1;
|
|
98
|
+
const swaps_before = n_swaps, lastloss = loss;
|
|
99
|
+
for (let j = 0; j < n; j++) {
|
|
100
|
+
if (j === lastswap) {
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
if (j === med[data[j].near.i]) {
|
|
104
|
+
continue; // This already is a medoid
|
|
105
|
+
}
|
|
106
|
+
const [change, b] = find_best_swap(mat, removal_loss, data, j);
|
|
107
|
+
if (change <= 0) {
|
|
108
|
+
continue; // No improvement
|
|
109
|
+
}
|
|
110
|
+
n_swaps += 1;
|
|
111
|
+
lastswap = j;
|
|
112
|
+
// perform the swap
|
|
113
|
+
loss = do_swap(mat, med, data, b, j);
|
|
114
|
+
update_removal_loss(data, removal_loss);
|
|
115
|
+
}
|
|
116
|
+
if (n_swaps === swaps_before || loss >= lastloss) {
|
|
117
|
+
break; // converged
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
let r = [0, USIZE_MAX];
|
|
121
|
+
for (let o = 0; o < removal_loss.length; o++) {
|
|
122
|
+
const remloss = removal_loss[o];
|
|
123
|
+
if (o === 0 || remloss < r[0]) {
|
|
124
|
+
r = [remloss, o];
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
loss = 1 - loss / n;
|
|
128
|
+
return_loss[k - minimum_k] = loss;
|
|
129
|
+
const assi = data.map((x) => x.near.i);
|
|
130
|
+
if (loss > best_loss) {
|
|
131
|
+
best_loss = loss;
|
|
132
|
+
return_assi = assi;
|
|
133
|
+
return_meds = med.slice();
|
|
134
|
+
}
|
|
135
|
+
return_swaps += n_swaps;
|
|
136
|
+
return_iter += iter;
|
|
137
|
+
loss = remove_med(mat, med, data, r[1]);
|
|
138
|
+
removal_loss.splice(r[1], 1);
|
|
139
|
+
k = med.length;
|
|
140
|
+
}
|
|
141
|
+
if (minimum_k <= 2) {
|
|
142
|
+
const { loss: loss2, assi: assi2, nIter: iter2, nSwaps: n_swaps2 } = fastermsc_k2(mat, med, maxiter);
|
|
143
|
+
return_loss[2 - minimum_k] = loss2;
|
|
144
|
+
if (loss2 > best_loss) {
|
|
145
|
+
best_loss = loss2;
|
|
146
|
+
return_meds = med.slice();
|
|
147
|
+
return_assi = assi2;
|
|
148
|
+
}
|
|
149
|
+
return_swaps += n_swaps2;
|
|
150
|
+
return_iter += iter2;
|
|
151
|
+
}
|
|
152
|
+
if (minimum_k <= 1) {
|
|
153
|
+
return_loss[0] = 0;
|
|
154
|
+
}
|
|
155
|
+
return { loss: best_loss, assi: return_assi, nIter: return_iter, nSwaps: return_swaps, meds: return_meds, losses: return_loss };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Update the third nearest medoid information. Called after each swap.
|
|
160
|
+
* Returns a fresh DistancePair.
|
|
161
|
+
*/
|
|
162
|
+
export function update_third_nearest_without_new(mat, med, n, s, b, o) {
|
|
163
|
+
let dist = new DistancePair(b, 0);
|
|
164
|
+
for (let i = 0; i < med.length; i++) {
|
|
165
|
+
const mi = med[i];
|
|
166
|
+
if (i === n || i === s) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
const d = mat.get(o, mi);
|
|
170
|
+
if (dist.i === b || d < dist.d) {
|
|
171
|
+
dist = new DistancePair(i, d);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return dist;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Remove one medoid. Returns RAW loss. */
|
|
178
|
+
export function remove_med(mat, med, data, b) {
|
|
179
|
+
const l = med.length - 1;
|
|
180
|
+
if (!(b < med.length)) throw new Error('invalid medoid number');
|
|
181
|
+
med[b] = med[l];
|
|
182
|
+
med.splice(l, 1);
|
|
183
|
+
let acc = 0;
|
|
184
|
+
for (let o = 0; o < data.length; o++) {
|
|
185
|
+
const reco = data[o];
|
|
186
|
+
if (reco.near.i === b) {
|
|
187
|
+
// nearest medoid is gone
|
|
188
|
+
if (reco.seco.i === l) {
|
|
189
|
+
reco.seco.i = b;
|
|
190
|
+
}
|
|
191
|
+
if (reco.third.i === l) {
|
|
192
|
+
reco.third.i = b;
|
|
193
|
+
}
|
|
194
|
+
reco.near = reco.seco.clone();
|
|
195
|
+
reco.seco = reco.third.clone();
|
|
196
|
+
reco.third = update_third_nearest_without_new(mat, med, reco.near.i, reco.seco.i, b, o);
|
|
197
|
+
} else if (reco.seco.i === b) {
|
|
198
|
+
// second nearest is gone
|
|
199
|
+
if (reco.near.i === l) {
|
|
200
|
+
reco.near.i = b;
|
|
201
|
+
}
|
|
202
|
+
if (reco.third.i === l) {
|
|
203
|
+
reco.third.i = b;
|
|
204
|
+
}
|
|
205
|
+
reco.seco = reco.third.clone();
|
|
206
|
+
reco.third = update_third_nearest_without_new(mat, med, reco.near.i, reco.seco.i, b, o);
|
|
207
|
+
} else if (reco.third.i === b) {
|
|
208
|
+
// third nearest is gone
|
|
209
|
+
if (reco.near.i === l) {
|
|
210
|
+
reco.near.i = b;
|
|
211
|
+
}
|
|
212
|
+
if (reco.seco.i === l) {
|
|
213
|
+
reco.seco.i = b;
|
|
214
|
+
}
|
|
215
|
+
reco.third = update_third_nearest_without_new(mat, med, reco.near.i, reco.seco.i, b, o);
|
|
216
|
+
} else {
|
|
217
|
+
if (reco.near.i === l) {
|
|
218
|
+
reco.near.i = b;
|
|
219
|
+
}
|
|
220
|
+
if (reco.seco.i === l) {
|
|
221
|
+
reco.seco.i = b;
|
|
222
|
+
}
|
|
223
|
+
if (reco.third.i === l) {
|
|
224
|
+
reco.third.i = b;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
acc += _loss(reco.near.d, reco.seco.d);
|
|
228
|
+
}
|
|
229
|
+
return acc;
|
|
230
|
+
}
|
|
231
|
+
</code></pre>
|
|
232
|
+
</article>
|
|
233
|
+
</section>
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
</div>
|
|
241
|
+
|
|
242
|
+
<br class="clear">
|
|
243
|
+
|
|
244
|
+
<footer>
|
|
245
|
+
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.
|
|
246
|
+
</footer>
|
|
247
|
+
|
|
248
|
+
<script>prettyPrint();</script>
|
|
249
|
+
<script src="scripts/polyfill.js"></script>
|
|
250
|
+
<script src="scripts/linenumber.js"></script>
|
|
251
|
+
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
</body>
|
|
255
|
+
</html>
|