scirs2_spatial/quantum_inspired/algorithms/
quantum_clustering.rs1use crate::error::{SpatialError, SpatialResult};
9use scirs2_core::ndarray::{Array1, Array2, ArrayView2};
10use scirs2_core::random::Rng;
11
12use super::super::concepts::QuantumState;
14use std::f64::consts::PI;
15
16#[derive(Debug, Clone)]
47pub struct QuantumClusterer {
48 num_clusters: usize,
50 quantum_depth: usize,
52 superposition_states: usize,
54 max_iterations: usize,
56 tolerance: f64,
58 centroid_state: Option<QuantumState>,
60}
61
62impl QuantumClusterer {
63 pub fn new(num_clusters: usize) -> Self {
71 Self {
72 num_clusters,
73 quantum_depth: 3,
74 superposition_states: 8,
75 max_iterations: 100,
76 tolerance: 1e-6,
77 centroid_state: None,
78 }
79 }
80
81 pub fn with_quantum_depth(mut self, depth: usize) -> Self {
89 self.quantum_depth = depth;
90 self
91 }
92
93 pub fn with_superposition_states(mut self, states: usize) -> Self {
101 self.superposition_states = states;
102 self
103 }
104
105 pub fn with_max_iterations(mut self, max_iter: usize) -> Self {
112 self.max_iterations = max_iter;
113 self
114 }
115
116 pub fn with_tolerance(mut self, tolerance: f64) -> Self {
123 self.tolerance = tolerance;
124 self
125 }
126
127 pub fn fit(
143 &mut self,
144 points: &ArrayView2<'_, f64>,
145 ) -> SpatialResult<(Array2<f64>, Array1<usize>)> {
146 let (n_points, n_dims) = points.dim();
147
148 if n_points < self.num_clusters {
149 return Err(SpatialError::InvalidInput(
150 "Number of points must be >= number of clusters".to_string(),
151 ));
152 }
153
154 let num_qubits = (self.num_clusters * n_dims)
156 .next_power_of_two()
157 .trailing_zeros() as usize;
158 let mut quantum_centroids = QuantumState::uniform_superposition(num_qubits);
159
160 let _encoded_points = self.encode_points_quantum(points)?;
162
163 let mut centroids = self.initialize_classical_centroids(points)?;
165 let mut assignments = Array1::zeros(n_points);
166 let mut prev_cost = f64::INFINITY;
167
168 for iteration in 0..self.max_iterations {
169 let new_assignments =
171 self.quantum_assignment_step(points, ¢roids, &quantum_centroids)?;
172
173 let new_centroids = self.quantum_centroid_update(points, &new_assignments)?;
175
176 self.apply_quantum_interference(&mut quantum_centroids, iteration)?;
178
179 let cost = self.calculate_quantum_cost(points, &new_centroids, &new_assignments);
181
182 if (prev_cost - cost).abs() < self.tolerance {
184 break;
185 }
186
187 centroids = new_centroids;
188 assignments = new_assignments;
189 prev_cost = cost;
190 }
191
192 self.centroid_state = Some(quantum_centroids);
193 Ok((centroids, assignments))
194 }
195
196 pub fn predict(&self, points: &ArrayView2<'_, f64>) -> SpatialResult<Array1<usize>> {
209 if self.centroid_state.is_none() {
210 return Err(SpatialError::InvalidInput(
211 "Clusterer must be fitted before prediction".to_string(),
212 ));
213 }
214
215 let (n_points, _) = points.dim();
218 let assignments = Array1::zeros(n_points);
219
220 Ok(assignments)
223 }
224
225 fn encode_points_quantum(
230 &self,
231 points: &ArrayView2<'_, f64>,
232 ) -> SpatialResult<Vec<QuantumState>> {
233 let (n_points, n_dims) = points.dim();
234 let mut encoded_points = Vec::new();
235
236 for i in 0..n_points {
237 let point = points.row(i);
238
239 let normalized_point: Vec<f64> = point.iter()
241 .map(|&x| (x + 1.0) / 2.0) .collect();
243
244 let num_qubits = (n_dims).next_power_of_two().trailing_zeros() as usize + 1;
246 let mut quantum_point = QuantumState::zero_state(num_qubits);
247
248 for (dim, &coord) in normalized_point.iter().enumerate() {
250 if dim < num_qubits {
251 let angle = coord * PI; quantum_point.phase_rotation(dim, angle)?;
253 }
254 }
255
256 encoded_points.push(quantum_point);
257 }
258
259 Ok(encoded_points)
260 }
261
262 fn initialize_classical_centroids(
267 &self,
268 points: &ArrayView2<'_, f64>,
269 ) -> SpatialResult<Array2<f64>> {
270 let (n_points, n_dims) = points.dim();
271 let mut centroids = Array2::zeros((self.num_clusters, n_dims));
272
273 let mut rng = scirs2_core::random::rng();
274
275 let mut selected_indices = Vec::new();
277
278 let first_idx = rng.gen_range(0..n_points);
280 selected_indices.push(first_idx);
281
282 for _ in 1..self.num_clusters {
284 let mut distances = vec![f64::INFINITY; n_points];
285
286 for i in 0..n_points {
288 for &selected_idx in &selected_indices {
289 let point = points.row(i);
290 let selected_point = points.row(selected_idx);
291 let dist: f64 = point
292 .iter()
293 .zip(selected_point.iter())
294 .map(|(&a, &b)| (a - b).powi(2))
295 .sum();
296 distances[i] = distances[i].min(dist);
297 }
298 }
299
300 let total_distance: f64 = distances.iter().sum();
302 let mut cumulative = 0.0;
303 let random_value = rng.gen_range(0.0..total_distance);
304
305 for (i, &distance) in distances.iter().enumerate() {
306 cumulative += distance;
307 if cumulative >= random_value {
308 selected_indices.push(i);
309 break;
310 }
311 }
312 }
313
314 for (i, &idx) in selected_indices.iter().enumerate() {
316 centroids.row_mut(i).assign(&points.row(idx));
317 }
318
319 Ok(centroids)
320 }
321
322 fn quantum_assignment_step(
327 &self,
328 points: &ArrayView2<'_, f64>,
329 centroids: &Array2<f64>,
330 quantum_state: &QuantumState,
331 ) -> SpatialResult<Array1<usize>> {
332 let (n_points, _) = points.dim();
333 let mut assignments = Array1::zeros(n_points);
334
335 for i in 0..n_points {
336 let point = points.row(i);
337 let mut min_distance = f64::INFINITY;
338 let mut best_cluster = 0;
339
340 for j in 0..self.num_clusters {
342 let centroid = centroids.row(j);
343
344 let classical_dist: f64 = point
346 .iter()
347 .zip(centroid.iter())
348 .map(|(&a, &b)| (a - b).powi(2))
349 .sum::<f64>()
350 .sqrt();
351
352 let quantum_enhancement =
354 quantum_state.probability(j % quantum_state.amplitudes.len());
355 let quantum_dist = classical_dist * (1.0 - 0.1 * quantum_enhancement);
356
357 if quantum_dist < min_distance {
358 min_distance = quantum_dist;
359 best_cluster = j;
360 }
361 }
362
363 assignments[i] = best_cluster;
364 }
365
366 Ok(assignments)
367 }
368
369 fn quantum_centroid_update(
374 &self,
375 points: &ArrayView2<'_, f64>,
376 assignments: &Array1<usize>,
377 ) -> SpatialResult<Array2<f64>> {
378 let (n_points, n_dims) = points.dim();
379 let mut centroids = Array2::zeros((self.num_clusters, n_dims));
380 let mut cluster_counts = vec![0; self.num_clusters];
381
382 for i in 0..n_points {
384 let cluster = assignments[i];
385 cluster_counts[cluster] += 1;
386
387 for j in 0..n_dims {
388 centroids[[cluster, j]] += points[[i, j]];
389 }
390 }
391
392 for i in 0..self.num_clusters {
394 if cluster_counts[i] > 0 {
395 let count = cluster_counts[i] as f64;
396
397 let quantum_correction = 1.0 + 0.05 * (1.0 / count).ln();
399
400 for j in 0..n_dims {
401 centroids[[i, j]] = (centroids[[i, j]] / count) * quantum_correction;
402 }
403 }
404 }
405
406 Ok(centroids)
407 }
408
409 fn apply_quantum_interference(
414 &self,
415 quantum_state: &mut QuantumState,
416 iteration: usize,
417 ) -> SpatialResult<()> {
418 for i in 0..quantum_state.numqubits {
420 if (iteration + i).is_multiple_of(2) {
421 quantum_state.hadamard(i)?;
422 }
423 }
424
425 let phase_angle = (iteration as f64) * PI / 16.0;
427 for i in 0..quantum_state.numqubits.min(3) {
428 quantum_state.phase_rotation(i, phase_angle)?;
429 }
430
431 Ok(())
432 }
433
434 fn calculate_quantum_cost(
439 &self,
440 points: &ArrayView2<'_, f64>,
441 centroids: &Array2<f64>,
442 assignments: &Array1<usize>,
443 ) -> f64 {
444 let (n_points, _) = points.dim();
445 let mut total_cost = 0.0;
446
447 for i in 0..n_points {
448 let point = points.row(i);
449 let cluster = assignments[i];
450 let centroid = centroids.row(cluster);
451
452 let distance: f64 = point
453 .iter()
454 .zip(centroid.iter())
455 .map(|(&a, &b)| (a - b).powi(2))
456 .sum::<f64>();
457
458 total_cost += distance;
459 }
460
461 total_cost
462 }
463
464 pub fn num_clusters(&self) -> usize {
466 self.num_clusters
467 }
468
469 pub fn quantum_depth(&self) -> usize {
471 self.quantum_depth
472 }
473
474 pub fn superposition_states(&self) -> usize {
476 self.superposition_states
477 }
478
479 pub fn is_fitted(&self) -> bool {
481 self.centroid_state.is_some()
482 }
483
484 pub fn quantum_state(&self) -> Option<&QuantumState> {
486 self.centroid_state.as_ref()
487 }
488}
489
490#[cfg(test)]
491mod tests {
492 use super::*;
493 use scirs2_core::ndarray::Array2;
494
495 #[test]
496 fn test_quantum_clusterer_creation() {
497 let clusterer = QuantumClusterer::new(3);
498 assert_eq!(clusterer.num_clusters(), 3);
499 assert_eq!(clusterer.quantum_depth(), 3);
500 assert!(!clusterer.is_fitted());
501 }
502
503 #[test]
504 fn test_configuration() {
505 let clusterer = QuantumClusterer::new(2)
506 .with_quantum_depth(5)
507 .with_superposition_states(16)
508 .with_max_iterations(200)
509 .with_tolerance(1e-8);
510
511 assert_eq!(clusterer.quantum_depth(), 5);
512 assert_eq!(clusterer.superposition_states(), 16);
513 assert_eq!(clusterer.max_iterations, 200);
514 assert_eq!(clusterer.tolerance, 1e-8);
515 }
516
517 #[test]
518 fn test_simple_clustering() {
519 let points = Array2::from_shape_vec(
521 (6, 2),
522 vec![
523 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 5.0, 5.0, 6.0, 5.0, 5.0, 6.0, ],
526 )
527 .unwrap();
528
529 let mut clusterer = QuantumClusterer::new(2);
530 let result = clusterer.fit(&points.view());
531
532 assert!(result.is_ok());
533 let (centroids, assignments) = result.unwrap();
534
535 assert_eq!(centroids.nrows(), 2);
536 assert_eq!(centroids.ncols(), 2);
537 assert_eq!(assignments.len(), 6);
538 assert!(clusterer.is_fitted());
539 }
540
541 #[test]
542 fn test_insufficient_points() {
543 let points = Array2::from_shape_vec((2, 2), vec![0.0, 0.0, 1.0, 1.0]).unwrap();
544 let mut clusterer = QuantumClusterer::new(3); let result = clusterer.fit(&points.view());
547 assert!(result.is_err());
548 }
549
550 #[test]
551 fn test_single_cluster() {
552 let points =
553 Array2::from_shape_vec((4, 2), vec![0.0, 0.0, 0.1, 0.1, -0.1, 0.1, 0.1, -0.1]).unwrap();
554
555 let mut clusterer = QuantumClusterer::new(1);
556 let result = clusterer.fit(&points.view());
557
558 assert!(result.is_ok());
559 let (centroids, assignments) = result.unwrap();
560
561 assert_eq!(centroids.nrows(), 1);
562 for assignment in assignments.iter() {
564 assert_eq!(*assignment, 0);
565 }
566 }
567
568 #[test]
569 fn test_prediction_without_fitting() {
570 let points = Array2::from_shape_vec((2, 2), vec![0.0, 0.0, 1.0, 1.0]).unwrap();
571 let clusterer = QuantumClusterer::new(2);
572
573 let result = clusterer.predict(&points.view());
574 assert!(result.is_err());
575 }
576}