1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
//! CLAM: Clustering with Associative Memory helpers.
//!
//! Based on Saha et al. (2023), "End-to-End Differentiable Clustering with AM".
//!
//! Requires the `hopfield` feature: `clump = { version = "...", features = ["hopfield"] }`.
//!
//! ## Overview
//!
//! CLAM uses Dense Associative Memory (AM) energy functions to define a
//! differentiable clustering objective. Points are "contracted" toward cluster
//! centroids via AM energy descent, and the clustering loss measures how much
//! each point moves during contraction.
//!
//! - [`am_assign`]: hard (nearest-centroid) cluster assignment
//! - [`am_soft_assign`]: soft (probabilistic) cluster assignment
//! - [`am_contract`]: contract a point toward centroids via AM energy descent
//! - [`clam_loss`]: differentiable clustering loss
/// Hard assignment of points to cluster centroids (nearest centroid by Euclidean distance).
///
/// # Example
///
/// ```rust
/// use clump::clam::am_assign;
///
/// let data = vec![vec![0.1f64, 0.1], vec![9.9, 9.9]];
/// let centroids = vec![vec![0.0f64, 0.0], vec![10.0, 10.0]];
/// let labels = am_assign(&data, ¢roids);
/// assert_eq!(labels, vec![0, 1]);
/// ```
/// Soft assignment (probabilities) of points to cluster centroids.
///
/// `P(i) = exp(-beta/2 ||x - c_i||^2) / Σ_j exp(-beta/2 ||x - c_j||^2)`
///
/// # Example
///
/// ```rust
/// use clump::clam::am_soft_assign;
///
/// let data = vec![vec![0.0f64, 0.0]];
/// let centroids = vec![vec![0.0f64, 0.0], vec![10.0, 10.0]];
/// let probs = am_soft_assign(&data, ¢roids, 1.0);
/// // Point at centroid 0 should assign mostly to centroid 0
/// assert!(probs[0][0] > probs[0][1]);
/// ```
/// Contract a point toward centroids using AM energy descent.
///
/// Uses the Log-Sum-Exp (LSE) energy from [`hopfield`]: gradient descent on
/// `E_β(v; Ξ) = -log Σ_μ exp(-β/2 ||v - ξ^μ||²)` converges toward the
/// nearest centroid attractor.
///
/// # Arguments
///
/// * `point` - Starting position
/// * `centroids` - Stored patterns (cluster centers)
/// * `beta` - Inverse temperature (larger = sharper contraction)
/// * `steps` - Maximum gradient descent iterations
/// * `lr` - Learning rate
///
/// # Example
///
/// ```rust
/// use clump::clam::am_contract;
///
/// let centroids = vec![vec![0.0f64, 0.0], vec![10.0, 10.0]];
/// let point = vec![0.5f64, 0.5];
/// let contracted = am_contract(&point, ¢roids, 2.0, 50, 0.1);
/// // Should move toward centroid 0
/// assert!(contracted[0] < point[0] + 0.01);
/// ```
/// Differentiable clustering loss (CLAM loss).
///
/// `L = Σ_i ||x_i - contract(x_i, centroids)||^2`
///
/// Measures how much each point moves during AM energy descent toward the
/// centroids. Lower loss means points are already near centroids (better
/// clustering). Can be used as a gradient-free objective for centroid search.
///
/// # Arguments
///
/// * `data` - Input data points
/// * `centroids` - Current cluster centroids
/// * `beta` - Inverse temperature for AM energy
/// * `steps` - Contraction steps per point
/// * `lr` - Learning rate for contraction