w-cluster 1.0.19 → 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 +18 -0
- package/src/WCluster.mjs +6 -2
- package/src/WClusterCore.mjs +30 -21
- package/src/WClusterMat.mjs +98 -24
- package/src/WPCAMat.mjs +2 -2
- 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,107 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<title>k-medoids/initialization.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/initialization.mjs</h1>
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
<section>
|
|
47
|
+
<article>
|
|
48
|
+
<pre class="prettyprint source linenums"><code>// Initialization helpers, ported from src/initialization.rs (RNG approximated for JS)
|
|
49
|
+
|
|
50
|
+
/** Use the first k objects (0..k-1) as initial medoids. */
|
|
51
|
+
export function first_k(k) {
|
|
52
|
+
return Array.from({ length: k }, (_, i) => i);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Sample k DISTINCT indices from [0, n) using Floyd's algorithm. rng: () => float in [0,1). */
|
|
56
|
+
export function sample(rng, n, k) {
|
|
57
|
+
if (k > n) throw new Error('`amount` of samples must be less than or equal to `length`');
|
|
58
|
+
const chosen = new Set();
|
|
59
|
+
const res = [];
|
|
60
|
+
for (let j = n - k; j < n; j++) {
|
|
61
|
+
const t = Math.floor(rng() * (j + 1)); // 0..j inclusive
|
|
62
|
+
const v = chosen.has(t) ? j : t;
|
|
63
|
+
chosen.add(v);
|
|
64
|
+
res.push(v);
|
|
65
|
+
}
|
|
66
|
+
return res;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Random initialization: k distinct medoid indices in [0, n). */
|
|
70
|
+
export function random_initialization(n, k, rng = Math.random) {
|
|
71
|
+
return sample(rng, n, k);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Fisher-Yates shuffle of [0, n). Used by rand_fasterpam / par_fasterpam. */
|
|
75
|
+
export function shuffle(rng, n) {
|
|
76
|
+
const a = Array.from({ length: n }, (_, i) => i);
|
|
77
|
+
for (let i = n - 1; i > 0; i--) {
|
|
78
|
+
const j = Math.floor(rng() * (i + 1));
|
|
79
|
+
const tmp = a[i]; a[i] = a[j]; a[j] = tmp;
|
|
80
|
+
}
|
|
81
|
+
return a;
|
|
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,275 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<title>k-medoids/pam.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/pam.mjs</h1>
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
<section>
|
|
47
|
+
<article>
|
|
48
|
+
<pre class="prettyprint source linenums"><code>// PAM (BUILD and SWAP) algorithm, ported 1:1 from src/pam.rs
|
|
49
|
+
import { arrayAdapter } from './arrayadapter.mjs';
|
|
50
|
+
import { Rec, DistancePair, U32_MAX, USIZE_MAX, choose_medoid_within_partition } from './util.mjs';
|
|
51
|
+
import { initial_assignment, do_swap } from './fasterpam.mjs';
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Run the original PAM SWAP algorithm (no BUILD, but given initial medoids).
|
|
55
|
+
*
|
|
56
|
+
* Provided for academic reasons to see the performance difference.
|
|
57
|
+
*
|
|
58
|
+
* @param mat - a pairwise distance matrix
|
|
59
|
+
* @param med - the list of medoids (mutated in place)
|
|
60
|
+
* @param maxiter - the maximum number of iterations allowed
|
|
61
|
+
* @returns { loss, assi, nIter, nSwaps }
|
|
62
|
+
*/
|
|
63
|
+
export function pam_swap(mat, med, maxiter) {
|
|
64
|
+
mat = arrayAdapter(mat);
|
|
65
|
+
const [loss, data] = initial_assignment(mat, med);
|
|
66
|
+
const [nloss, assi, nIter, nSwaps] = pam_optimize(mat, med, data, maxiter, loss);
|
|
67
|
+
return { loss: nloss, assi, nIter, nSwaps };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Run the original PAM BUILD algorithm.
|
|
72
|
+
*
|
|
73
|
+
* Provided for academic reasons to see the performance difference.
|
|
74
|
+
*
|
|
75
|
+
* @param mat - a pairwise distance matrix
|
|
76
|
+
* @param k - the number of medoids to pick
|
|
77
|
+
* @returns { loss, assi, meds }
|
|
78
|
+
*/
|
|
79
|
+
export function pam_build(mat, k) {
|
|
80
|
+
mat = arrayAdapter(mat);
|
|
81
|
+
const n = mat.len();
|
|
82
|
+
if (!mat.isSquare()) throw new Error('Dissimilarity matrix is not square');
|
|
83
|
+
if (!(n <= U32_MAX)) throw new Error('N is too large');
|
|
84
|
+
if (!(k > 0 && k < U32_MAX)) throw new Error('invalid N');
|
|
85
|
+
if (!(k <= n)) throw new Error('k must be at most N');
|
|
86
|
+
const meds = [];
|
|
87
|
+
const data = [];
|
|
88
|
+
const loss = pam_build_initialize(mat, meds, data, k);
|
|
89
|
+
const assi = data.map((x) => x.near.i);
|
|
90
|
+
return { loss, assi, meds };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Run the original PAM algorithm (BUILD and SWAP).
|
|
95
|
+
*
|
|
96
|
+
* Provided for academic reasons to see the performance difference.
|
|
97
|
+
*
|
|
98
|
+
* @param mat - a pairwise distance matrix
|
|
99
|
+
* @param k - the number of medoids to pick
|
|
100
|
+
* @param maxiter - the maximum number of iterations allowed
|
|
101
|
+
* @returns { loss, assi, meds, nIter, nSwaps }
|
|
102
|
+
*/
|
|
103
|
+
export function pam(mat, k, maxiter) {
|
|
104
|
+
mat = arrayAdapter(mat);
|
|
105
|
+
const n = mat.len();
|
|
106
|
+
if (!mat.isSquare()) throw new Error('Dissimilarity matrix is not square');
|
|
107
|
+
if (!(n <= U32_MAX)) throw new Error('N is too large');
|
|
108
|
+
if (!(k > 0 && k < U32_MAX)) throw new Error('invalid N');
|
|
109
|
+
if (!(k <= n)) throw new Error('k must be at most N');
|
|
110
|
+
const meds = [];
|
|
111
|
+
const data = [];
|
|
112
|
+
const loss = pam_build_initialize(mat, meds, data, k);
|
|
113
|
+
const [nloss, assi, nIter, nSwaps] = pam_optimize(mat, meds, data, maxiter, loss);
|
|
114
|
+
return { loss: nloss, assi, meds, nIter, nSwaps }; // also return medoids
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/** Main optimization function of PAM, not exposed (use pam_swap or pam). Returns [loss, assi, nIter, nSwaps]. */
|
|
118
|
+
function pam_optimize(mat, med, data, maxiter, loss) {
|
|
119
|
+
const n = mat.len(), k = med.length;
|
|
120
|
+
if (k === 1) {
|
|
121
|
+
const assi = new Array(n).fill(0);
|
|
122
|
+
const [swapped, sloss] = choose_medoid_within_partition(mat, assi, med, 0);
|
|
123
|
+
return [sloss, assi, 1, swapped ? 1 : 0];
|
|
124
|
+
}
|
|
125
|
+
let n_swaps = 0, iter = 0;
|
|
126
|
+
while (iter < maxiter) {
|
|
127
|
+
iter += 1;
|
|
128
|
+
let best = [0, k, USIZE_MAX];
|
|
129
|
+
for (let j = 0; j < n; j++) {
|
|
130
|
+
if (j === med[data[j].near.i]) {
|
|
131
|
+
continue; // This already is a medoid
|
|
132
|
+
}
|
|
133
|
+
const [change, b] = find_best_swap_pam(mat, med, data, j);
|
|
134
|
+
if (change >= best[0]) {
|
|
135
|
+
continue; // No improvement
|
|
136
|
+
}
|
|
137
|
+
best = [change, b, j];
|
|
138
|
+
}
|
|
139
|
+
if (best[0] < 0) {
|
|
140
|
+
n_swaps += 1;
|
|
141
|
+
// perform the swap
|
|
142
|
+
const newloss = do_swap(mat, med, data, best[1], best[2]);
|
|
143
|
+
if (newloss >= loss) {
|
|
144
|
+
break; // Probably numerically unstable now.
|
|
145
|
+
}
|
|
146
|
+
loss = newloss;
|
|
147
|
+
} else {
|
|
148
|
+
break; // No improvement, or NaN.
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
const assi = data.map((x) => x.near.i);
|
|
152
|
+
return [loss, assi, iter, n_swaps];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Find the best swap for object j - slower PAM version. Returns [acc_best, m_best]. */
|
|
156
|
+
function find_best_swap_pam(mat, med, data, j) {
|
|
157
|
+
const recj = data[j];
|
|
158
|
+
let best = [0, USIZE_MAX];
|
|
159
|
+
for (let m = 0; m < med.length; m++) {
|
|
160
|
+
let acc = -recj.near.d; // j becomes medoid
|
|
161
|
+
for (let o = 0; o < data.length; o++) {
|
|
162
|
+
const reco = data[o];
|
|
163
|
+
if (o === j) {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
const doj = mat.get(o, j);
|
|
167
|
+
// Current medoid is being replaced:
|
|
168
|
+
if (reco.near.i === m) {
|
|
169
|
+
if (doj < reco.seco.d) {
|
|
170
|
+
// Assign to new medoid:
|
|
171
|
+
acc += doj - reco.near.d;
|
|
172
|
+
} else {
|
|
173
|
+
// Assign to second nearest instead:
|
|
174
|
+
acc += reco.seco.d - reco.near.d;
|
|
175
|
+
}
|
|
176
|
+
} else if (doj < reco.near.d) {
|
|
177
|
+
// new mediod is closer:
|
|
178
|
+
acc += doj - reco.near.d;
|
|
179
|
+
} // else no change
|
|
180
|
+
}
|
|
181
|
+
if (acc < best[0]) {
|
|
182
|
+
best = [acc, m];
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
return best;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Not exposed. Use pam_build or pam. Pushes into medsArr (numbers) and dataArr (Rec). Returns loss. */
|
|
189
|
+
export function pam_build_initialize(mat, meds, data, k) {
|
|
190
|
+
const n = mat.len();
|
|
191
|
+
if (!mat.isSquare()) throw new Error('Dissimilarity matrix is not square');
|
|
192
|
+
// choose first medoid
|
|
193
|
+
let best = [0, k];
|
|
194
|
+
for (let i = 0; i < n; i++) {
|
|
195
|
+
let sum = 0;
|
|
196
|
+
for (let j = 0; j < n; j++) {
|
|
197
|
+
if (j !== i) {
|
|
198
|
+
sum += mat.get(j, i);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
if (i === 0 || sum < best[0]) {
|
|
202
|
+
best = [sum, i];
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
let loss = best[0];
|
|
206
|
+
meds.push(best[1]);
|
|
207
|
+
for (let j = 0; j < n; j++) {
|
|
208
|
+
data.push(new Rec(0, mat.get(j, best[1]), U32_MAX, 0));
|
|
209
|
+
}
|
|
210
|
+
// choose remaining medoids
|
|
211
|
+
for (let l = 1; l < k; l++) {
|
|
212
|
+
best = [0, k];
|
|
213
|
+
for (let i = 0; i < data.length; i++) {
|
|
214
|
+
let sum = -data[i].near.d;
|
|
215
|
+
for (let j = 0; j < data.length; j++) {
|
|
216
|
+
const dj = data[j];
|
|
217
|
+
if (j !== i) {
|
|
218
|
+
const d = mat.get(j, i);
|
|
219
|
+
if (d < dj.near.d) {
|
|
220
|
+
sum += d - dj.near.d;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (i === 0 || sum < best[0]) {
|
|
225
|
+
best = [sum, i];
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (best[0] >= 0) { break; } // No further improvements - duplicates etc.
|
|
229
|
+
// Update assignments:
|
|
230
|
+
loss = 0;
|
|
231
|
+
for (let j = 0; j < data.length; j++) {
|
|
232
|
+
const recj = data[j];
|
|
233
|
+
if (j === best[1]) {
|
|
234
|
+
recj.seco = recj.near.clone();
|
|
235
|
+
recj.near = new DistancePair(l, 0);
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
const dj = mat.get(j, best[1]);
|
|
239
|
+
if (dj < recj.near.d) {
|
|
240
|
+
recj.seco = recj.near.clone();
|
|
241
|
+
recj.near = new DistancePair(l, dj);
|
|
242
|
+
} else if (recj.seco.i === U32_MAX || dj < recj.seco.d) {
|
|
243
|
+
recj.seco = new DistancePair(l, dj);
|
|
244
|
+
}
|
|
245
|
+
loss += recj.near.d;
|
|
246
|
+
}
|
|
247
|
+
meds.push(best[1]);
|
|
248
|
+
}
|
|
249
|
+
return loss;
|
|
250
|
+
}
|
|
251
|
+
</code></pre>
|
|
252
|
+
</article>
|
|
253
|
+
</section>
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
|
|
260
|
+
</div>
|
|
261
|
+
|
|
262
|
+
<br class="clear">
|
|
263
|
+
|
|
264
|
+
<footer>
|
|
265
|
+
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.
|
|
266
|
+
</footer>
|
|
267
|
+
|
|
268
|
+
<script>prettyPrint();</script>
|
|
269
|
+
<script src="scripts/polyfill.js"></script>
|
|
270
|
+
<script src="scripts/linenumber.js"></script>
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
</body>
|
|
275
|
+
</html>
|