///|
/// Bioconductor apcluster Demo - Affinity Propagation Clustering.
///
/// Demonstrates the port of the Bioconductor `apcluster` package
/// (Frey & Dueck, Science 2007): four similarity functions
/// (negDistMat / expSimMat / linSimMat / corSimMat), the default median-
/// preference run, the fixed-cluster-count apclusterK bisection, the
/// preference controls, and the deterministic noise runs.
///|
/// Four tight 2D blobs of six points each (deterministic jitter).
fn demo_blobs() -> Array[Array[Double]] {
[
[-0.12545988, 0.45071431],
[0.23199394, 0.09865848],
[-0.34398136, -0.34400548],
[-0.44191639, 0.36617615],
[0.10111501, 0.20807258],
[-0.47941551, 0.46990985],
[10.33244264, 9.71233911],
[9.68182497, 9.68340451],
[9.80424224, 10.02475643],
[9.93194502, 9.79122914],
[10.11185289, 9.63949386],
[9.79214465, 9.86636184],
[-0.04393002, 10.28517596],
[-0.30032622, 10.01423444],
[0.09241457, 9.54645041],
[0.10754485, 9.67052412],
[-0.43494841, 10.44888554],
[0.46563203, 10.30839735],
[9.80461377, -0.40232789],
[10.18423303, -0.05984751],
[9.62203823, -0.00482309],
[9.53438852, 0.40932040],
[9.75877998, 0.16252228],
[9.81171108, 0.02006802],
]
}
///|
fn cluster_sizes(res : @src.ApResult) -> Array[Int32] {
let out : Array[Int32] = []
for members in res.clusters() {
out.push(members.length())
}
out
}
///|
fn print_result(title : String, res : @src.ApResult) -> Unit {
println("--- \{title} ---")
println("exemplars (0-based): \{Repr(res.exemplars())}")
println("cluster sizes: \{Repr(cluster_sizes(res))}")
println("iters: \{res.iters()} converged: \{res.converged()}")
println(
"netsim: \{res.netsim()} (dpsim=\{res.dpsim()}, expref=\{res.expref()})",
)
println("")
}
///|
fn main raise {
let x = demo_blobs()
println("=== 1. negDistMat (negative squared Euclidean) ===")
let s = @src.ap_neg_dist_mat(x)
let r = @src.apcluster(sim=s)
println("median preference: \{r.preferences()[0]}")
print_result("default apcluster", r)
println("=== 2. Other distance-based similarities ===")
let re = @src.apcluster(sim=@src.ap_exp_sim_mat(x))
print_result("expSimMat (Gaussian kernel)", re)
let rl = @src.apcluster(sim=@src.ap_lin_sim_mat(x))
print_result("linSimMat (linear)", rl)
println("=== 3. apclusterK: target a fixed number of clusters ===")
let k5 = @src.apcluster_k(sim=s, k=5)
print_result("apclusterK(k=5)", k5)
let k3 = @src.apcluster_k(sim=s, k=3)
println("--- apclusterK(k=3): bisection collapses, prune merge ---")
println(
"exemplars: \{Repr(k3.exemplars())} sizes: \{Repr(cluster_sizes(k3))}",
)
println("")
println("=== 4. Preference controls cluster granularity ===")
// high preference: every point wants to be an exemplar
let hi = @src.apcluster(sim=s, p=Some(10.0))
println("p=+10 -> clusters: \{hi.num_clusters()}")
// very low preference: a single consensus exemplar
let lo = @src.apcluster(sim=s, p=Some(-100000.0))
println(
"p=-1e5 -> clusters: \{lo.num_clusters()} exemplar: \{Repr(lo.exemplars())}",
)
println("")
println("=== 5. corSimMat: co-expression style grouping ===")
let z = [
[1.0, 2.0, 3.0, 4.0, 5.0],
[1.1, 1.9, 3.1, 3.9, 5.2],
[5.0, 4.0, 3.0, 2.0, 1.0],
[5.1, 3.8, 2.9, 2.1, 0.9],
]
let rc = @src.apcluster(sim=@src.ap_cor_sim_mat(z))
print_result("corSimMat (Pearson)", rc)
println("=== 6. Noise runs (deterministic with apcluster_seed) ===")
@src.apcluster_seed(2026)
let rn = @src.apcluster(sim=s, nonoise=5)
println(
"nonoise=5 -> clusters: \{rn.num_clusters()} exemplars: \{Repr(rn.exemplars())}",
)
}